递归列出Java中的文件


257

如何在Java目录中递归列出所有文件?框架是否提供任何实用程序?

我看到了很多hacky的实现。但是框架或nio都没有


2
我刚刚完成了测试结果,可以为许多答案提供性能测试。毫不奇怪,所有基于NIO的答案都表现最佳。commons-io的答案显然是表现最差的产品,其运行时间是其两倍以上。
Brett Ryan

2
Java8:Files.walk吗?

Answers:


326

Java 8提供了一个不错的流来处理树中的所有文件。

Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)
        .forEach(System.out::println);

这提供了一种遍历文件的自然方法。由于它是流,因此您可以对结果执行所有不错的流操作,例如限制,分组,映射,提早退出等。

更新:我可能会指出,还有Files.find带有BiPredicate,如果您需要检查文件属性,它可能会更高效。

Files.find(Paths.get(path),
           Integer.MAX_VALUE,
           (filePath, fileAttr) -> fileAttr.isRegularFile())
        .forEach(System.out::println);

请注意,尽管JavaDoc排除了此方法可能比Files.walk更有效的方法,但实际上是相同的,但是如果您还在过滤器中检索文件属性,则可以观察到性能差异。最后,如果需要过滤属性,请使用Files.find,否则请使用Files.walk,这主要是因为有重载并且更方便。

测试:根据要求,我提供了许多答案的性能比较。检出包含结果和测试用例Github项目


6
这些示例之一即使对于初学者也可以显示函数式编程的魔力。
约翰尼

2
与Java 8之前的方法相比,此方法的性能如何?我当前的目录遍历速度太慢,我在寻找可以加快速度的方法。
Sridhar Sarnobat,2015年

1
我正在提供一些包含所提供答案中大多数变体的测试。到目前为止,Files.walk与并行流一起使用似乎是最好的选择,紧随其后的Files.walkFileTree只是稍微慢一点。根据我的测试,使用commons-io接受的答案是最慢的,慢了4倍。
Brett Ryan

1
@BrettRyan,我尝试了您的解决方案,但出现异常Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException。我怎么能改正
Kachna

5
如何从中获取文件的实际列表?
thouliha 2015年

159

FileUtils具有iterateFileslistFiles方法。试试看。(来自commons-io

编辑:您可以在此处查看不同方法的基准。commons-io方法似乎很慢,因此请从此处选择一些较快的方法(如果有的话)


40
FYI / TLDR:如果您只想递归列出所有文件而不进行过滤,请执行FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE),其中dir是一个指向基本目录的File对象。
andronikus 2012年

2
您可能要考虑使用listFilesAndDirs(),因为listFiles()它不会返回空文件夹。
schnatterer

1
@MikeFHay查看FileUtils代码,我认为应该是FileUtils.listFiles(dir, true, true)。使用FileUtils.listFiles(dir, null, true)将引发异常,而FileUtils.listFiles(dir, true, null)将列出所有文件而不查看子目录。
ocramot

JDK本机库怎么样?我可以轻松实现这一目标,但我只是其他地方的C&P
Christian Bongiorno 2014年

1
我正在进行一些测试,但是到目前为止,这似乎比使用JDK8或JDK7替代方法的性能慢4倍。事实证明,这种方法也存在符号链接的问题,特别是当它们链接到树中更高的目录时,这会导致该方法永不返回,可以通过处理过滤器来避免这种情况,但是不幸的是,即使在以下情况下,符号链接本身也不会被访问一份文件。
Brett Ryan

138

//准备运行

import java.io.File;

public class Filewalker {

    public void walk( String path ) {

        File root = new File( path );
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f.getAbsolutePath() );
                System.out.println( "Dir:" + f.getAbsoluteFile() );
            }
            else {
                System.out.println( "File:" + f.getAbsoluteFile() );
            }
        }
    }

    public static void main(String[] args) {
        Filewalker fw = new Filewalker();
        fw.walk("c:\\" );
    }

}

9
请注意,对于指向路径层次结构中较高路径的符号链接,将导致该方法永无止境。考虑带有符号链接的路径指向-> .
Brett Ryan

2
从本质上来说,这只是Files.walkFileTree的错误实现。我建议人们看看FIles.walkFileTree而不是尝试自己动手...它已经处理了@BrettRyan指出的确切问题。
泰勒·尼科尔斯

感谢您包含import java.io.File;。如此多的示例忘记了包含名称空间的内容,甚至包括数据类型的内容,从而使该示例成为探索之旅的起点。在这里,该示例已准备好运行。谢谢。
barrypicker

路径可能取决于Filewalker文件所在的位置。使用"/""./""../"用于根目录,当前工作目录,以及父目录,分别为
摩西Kirathe

67

Java 7 具有Files.walkFileTree

如果提供起点和文件访问者,它将在文件访问者浏览文件树中的文件时调用文件访问者的各种方法。我们希望人们在开发递归副本,递归移动,递归删除或设置权限或对每个文件执行其他操作的递归操作时使用此功能。

现在有关于此问题的完整Oracle教程


而且它从不通知步行结束。
Farid

25

无需外部库。
返回一个Collection,因此您可以在调用后对其进行任何处理。

public static Collection<File> listFileTree(File dir) {
    Set<File> fileTree = new HashSet<File>();
    if(dir==null||dir.listFiles()==null){
        return fileTree;
    }
    for (File entry : dir.listFiles()) {
        if (entry.isFile()) fileTree.add(entry);
        else fileTree.addAll(listFileTree(entry));
    }
    return fileTree;
}

简单干净
Leo

17

我会喜欢这样的:

public void list(File file) {
    System.out.println(file.getName());
    File[] children = file.listFiles();
    for (File child : children) {
        list(child);
    }
}

System.out.println只是用于指示对该文件执行某些操作。无需区分文件和目录,因为普通文件仅具有零个子代。


6
来自以下文档listFiles():“如果此抽象路径名不表示目录,则此方法返回null。”
hfs

改进的变体public static Collection <File> listFileTree(File dir){if(null == dir ||!dir.isDirectory()){return Collections.emptyList(); }最终Set <File> fileTree = new HashSet <File>(); 对于(文件条目:dir.listFiles()){如果(entry.isFile()){fileTree.add(entry); } else {fileTree.addAll(listFileTree(entry)); 返回文件树;}
2013年

对我来说,这是最简洁的递归答案。
WillieT

14

对于这种简单的遍历,我更喜欢使用队列而不是递归:

List<File> allFiles = new ArrayList<File>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File("/start/dir/"));
while (!dirs.isEmpty()) {
  for (File f : dirs.poll().listFiles()) {
    if (f.isDirectory()) {
      dirs.add(f);
    } else if (f.isFile()) {
      allFiles.add(f);
    }
  }
}

但是您的算法无法以缩进输出进行打印。污垢和文件混乱。有什么办法吗?

12

只需使用简单的递归自己编写即可:

public List<File> addFiles(List<File> files, File dir)
{
    if (files == null)
        files = new LinkedList<File>();

    if (!dir.isDirectory())
    {
        files.add(dir);
        return files;
    }

    for (File file : dir.listFiles())
        addFiles(files, file);
    return files;
}

1
请!让调用者初始化文件列表,这样就不必每次都检查其无效性。如果要创建另一个创建列表的(公共)方法,请调用此内部方法并返回完整列表。
helios 2010年

1
随你。空支票不是很昂贵,除了便利性和个人喜好之外,我认为他会明白这一点的。
pstanton 2010年

您能再详细一点吗?
uday 2013年

8

我认为这应该可以完成工作:

File dir = new File(dirname);
String[] files = dir.list();

这样,您将拥有文件和目录。现在使用递归,对dirs进行相同的操作(File类具有isDirectory()方法)。


8

使用Java 7,可以使用以下类:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class MyFileIterator extends SimpleFileVisitor<Path>
{
    public MyFileIterator(String path) throws Exception
    {
        Files.walkFileTree(Paths.get(path), this);
    }

    @Override
    public FileVisitResult visitFile(Path file,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("File: " + file);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("Dir: " + dir);
        return FileVisitResult.CONTINUE;
    }
}

7

在Java 8中,我们现在可以使用“文件”实用程序来遍历文件树。很简单。

Files.walk(root.toPath())
      .filter(path -> !Files.isDirectory(path))
      .forEach(path -> System.out.println(path));

7

该代码可以运行了

public static void main(String... args) {
    File[] files = new File("D:/").listFiles();
    if (files != null) 
       getFiles(files);
}

public static void getFiles(File[] files) {
    for (File file : files) {
        if (file.isDirectory()) {
            getFiles(file.listFiles());
        } else {
            System.out.println("File: " + file);
        }
    }
}

4

除了递归遍历之外,还可以使用基于访问者的方法。

下面的代码使用基于访问者的方法进行遍历。期望程序的输入是要遍历的根目录。

public interface Visitor {
    void visit(DirElement d);
    void visit(FileElement f);
}

public abstract class Element {
    protected File rootPath;
    abstract void accept(Visitor v);

    @Override
    public String toString() {
        return rootPath.getAbsolutePath();
    }
}

public class FileElement extends Element {
    FileElement(final String path) {
        rootPath = new File(path);
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }
}

public class DirElement extends Element implements Iterable<Element> {
    private final List<Element> elemList;
    DirElement(final String path) {
        elemList = new ArrayList<Element>();
        rootPath = new File(path);
        for (File f : rootPath.listFiles()) {
            if (f.isDirectory()) {
                elemList.add(new DirElement(f.getAbsolutePath()));
            } else if (f.isFile()) {
                elemList.add(new FileElement(f.getAbsolutePath()));
            }
        }
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }

    public Iterator<Element> iterator() {
        return elemList.iterator();
    }
}

public class ElementWalker {
    private final String rootDir;
    ElementWalker(final String dir) {
        rootDir = dir;
    }

    private void traverse() {
        Element d = new DirElement(rootDir);
        d.accept(new Walker());
    }

    public static void main(final String[] args) {
        ElementWalker t = new ElementWalker("C:\\temp");
        t.traverse();
    }

    private class Walker implements Visitor {
        public void visit(final DirElement d) {
            System.out.println(d);
            for(Element e:d) {
                e.accept(this);
            }
        }

        public void visit(final FileElement f) {
            System.out.println(f);
        }
    }
}

3

您可以使用以下代码递归获取特定文件夹或目录的文件列表。

public static void main(String args[]) {

        recusiveList("D:");

    }

    public static void recursiveList(String path) {

        File f = new File(path);
        File[] fl = f.listFiles();
        for (int i = 0; i < fl.length; i++) {
            if (fl[i].isDirectory() && !fl[i].isHidden()) {
                System.out.println(fl[i].getAbsolutePath());
                recusiveList(fl[i].getAbsolutePath());
            } else {
                System.out.println(fl[i].getName());
            }
        }
    }

2

可以接受的答案很好,但是当您要在lambda中进行IO时,答案就会崩溃。

如果您的操作声明IOExceptions,则可以执行以下操作。

您可以将过滤后的流视为Iterable,然后在常规的for-each循环中执行操作。这样,您不必在lambda内部处理异常。

try (Stream<Path> pathStream = Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)) {

    for (Path file : (Iterable<Path>) pathStream::iterator) {
        // something that throws IOException
        Files.copy(file, System.out);
    }
}

在这里发现了这个技巧:https : //stackoverflow.com/a/32668807/1207791


1

具有单个列表的非递归BFS(特定示例是搜索* .eml文件):

    final FileFilter filter = new FileFilter() {
        @Override
        public boolean accept(File file) {
            return file.isDirectory() || file.getName().endsWith(".eml");
        }
    };

    // BFS recursive search
    List<File> queue = new LinkedList<File>();
    queue.addAll(Arrays.asList(dir.listFiles(filter)));

    for (ListIterator<File> itr = queue.listIterator(); itr.hasNext();) {
        File file = itr.next();
        if (file.isDirectory()) {
            itr.remove();
            for (File f: file.listFiles(filter)) itr.add(f);
        }
    }

1

我的版本(当然我可以使用Java 8中的内置walk ;-)):

public static List<File> findFilesIn(File rootDir, Predicate<File> predicate) {
        ArrayList<File> collected = new ArrayList<>();
        walk(rootDir, predicate, collected);
        return collected;
    }

    private static void walk(File dir, Predicate<File> filterFunction, List<File> collected) {
        Stream.of(listOnlyWhenDirectory(dir))
                .forEach(file -> walk(file, filterFunction, addAndReturn(collected, file, filterFunction)));
    }

    private static File[] listOnlyWhenDirectory(File dir) {
        return dir.isDirectory() ? dir.listFiles() : new File[]{};
    }

    private static List<File> addAndReturn(List<File> files, File toAdd, Predicate<File> filterFunction) {
        if (filterFunction.test(toAdd)) {
            files.add(toAdd);
        }
        return files;
    }

1

这里是一个简单但完美的解决方案,使用recursion

public static List<Path> listFiles(String rootDirectory)
{
    List<Path> files = new ArrayList<>();
    listFiles(rootDirectory, files);

    return files;
}

private static void listFiles(String path, List<Path> collectedFiles)
{
    File root = new File(path);
    File[] files = root.listFiles();

    if (files == null)
    {
        return;
    }

    for (File file : files)
    {
        if (file.isDirectory())
        {
            listFiles(file.getAbsolutePath(), collectedFiles);
        } else
        {
            collectedFiles.add(file.toPath());
        }
    }
}

1
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }

1

我想出了这个用于递归打印所有文件/文件名的方法。

private static void printAllFiles(String filePath,File folder) {
    if(filePath==null) {
        return;
    }
    File[] files = folder.listFiles();
    for(File element : files) {
        if(element.isDirectory()) {
            printAllFiles(filePath,element);
        } else {
            System.out.println(" FileName "+ element.getName());
        }
    }
}

0

示例使用来自java.nio的Files.find()在目录递归搜索子目录中输出* .csv文件:

String path = "C:/Daten/ibiss/ferret/";
    logger.debug("Path:" + path);
    try (Stream<Path> fileList = Files.find(Paths.get(path), Integer.MAX_VALUE,
            (filePath, fileAttr) -> fileAttr.isRegularFile() && filePath.toString().endsWith("csv"))) {
        List<String> someThingNew = fileList.sorted().map(String::valueOf).collect(Collectors.toList());
        for (String t : someThingNew) {
            t.toString();
            logger.debug("Filename:" + t);
        }

    }

发布此示例,因为在理解Bryan给出的#1示例中如何传递filename参数时遇到了麻烦,因此在流结果上使用foreach-

希望这可以帮助。


0

科特林FileTreeWalk为此目的。例如:

dataDir.walkTopDown().filter { !it.isDirectory }.joinToString("\n") {
   "${it.toRelativeString(dataDir)}: ${it.length()}"
}

将在给定的根目录下生成所有非目录文件的文本列表,每行一个文件,相对于根目录和长度的路径。


0

即使有人已经提供Java 8 walk,您也可以执行另一种方法。

这将递归为您提供所有文件

  private Stream<File> files(File file) {
    return file.isDirectory()
            ? Arrays.stream(file.listFiles()).flatMap(this::files)
            : Stream.of(file);
}

-1

基于堆高机答案。这是一个无需任何外部库即可在JSP中工作的解决方案,因此您可以将其几乎放置在服务器上的任何位置:

<!DOCTYPE html>
<%@ page session="false" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page contentType="text/html; charset=UTF-8" %>

<%!
    public List<String> files = new ArrayList<String>();
    /**
        Fills files array with all sub-files.
    */
    public void walk( File root ) {
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f );
            }
            else {
                files.add(f.getAbsolutePath());
            }
        }
    }
%>
<%
    files.clear();
    File jsp = new File(request.getRealPath(request.getServletPath()));
    File dir = jsp.getParentFile();
    walk(dir);
    String prefixPath = dir.getAbsolutePath() + "/";
%>

然后,您只需执行以下操作:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>

1
虽然可能可行,但问题在于文件浏览,而不是呈现已浏览文件。更好地公开您的算法,不建议将业务逻辑嵌入JSP中。
塞缪尔·克瑞恩

那取决于你在做什么。在企业规模的应用程序中,您绝对正确。如果您只需要将它作为简单的独立列表的插入项,那么这很好。
Nux
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.