如何在Java中迭代遍历目录及其子目录中的文件?

203

我需要获取一个目录中所有文件的列表,包括所有子目录中的文件。如何使用Java实现目录迭代的标准方式?

10个回答

246
你可以使用File#isDirectory()来测试给定的文件(路径)是否是一个目录。如果这是true,那么你只需要再次调用相同的方法,使用它的File#listFiles()结果。这被称为递归
这是一个基本的起步示例:
package com.stackoverflow.q3154488;

import java.io.File;

public class Demo {

    public static void main(String... args) {
        File dir = new File("/path/to/dir");
        showFiles(dir.listFiles());
    }

    public static void showFiles(File[] files) {
        for (File file : files) {
            if (file.isDirectory()) {
                System.out.println("Directory: " + file.getAbsolutePath());
                showFiles(file.listFiles()); // Calls same method again.
            } else {
                System.out.println("File: " + file.getAbsolutePath());
            }
        }
    }
}

请注意,当树的深度超过JVM的堆栈容量时,这可能会导致StackOverflowError。如果您已经使用Java 8或更新版本,则最好使用Files#walk(),它利用了尾递归
package com.stackoverflow.q3154488;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class DemoWithJava8 {

    public static void main(String... args) throws Exception {
        Path dir = Paths.get("/path/to/dir");
        Files.walk(dir).forEach(path -> showFile(path.toFile()));
    }

    public static void showFile(File file) {
        if (file.isDirectory()) {
            System.out.println("Directory: " + file.getAbsolutePath());
        } else {
            System.out.println("File: " + file.getAbsolutePath());
        }
    }
}

谢谢Balus,你有大致的猜测这可能有多深吗? - James
11
取决于您的JVM内存设置,但通常为几千个。如果您认为可能会遇到这样的目录,请不要使用递归。 - Mike Baranczak
4
如果文件系统在调用 isDirectorylistFiles 之间发生更改,就可能导致出现 NullPointerException 错误,例如如果 System.out.println 阻塞或运气不佳。检查 listFiles 的输出结果是否为 null 可以解决这个竞态条件。 - Mike Samuel
@MikeSamuel - 如果目录中有数万个带有较长名称的小文件,并且您一次性将所有文件名都存储在数组中,那么JVM可能会耗尽内存,对吧?那么,是否有一种逐个或分批迭代的方法呢? - Erran Morad
1
@BoratSagdiyev,如果你使用的是现代JVM,那么不要使用旧的Java文件API,而应使用java.nio.file.DirectoryStream 迭代一个目录,可以实现小内存占用,但确定内存占用情况的唯一方法是在特定平台上监视内存使用情况。 - Mike Samuel
关于使用 DirectoryStream 的注释,请参考 Wim Deblauwe 的答案,因为它列出了重要的方面。应该关闭流以防止资源泄漏。 - Svilen

100
如果您正在使用Java 1.7,您可以使用java.nio.file.Files.walkFileTree(...)
例如:
public class WalkFileTreeExample {

  public static void main(String[] args) {
    Path p = Paths.get("/usr");
    FileVisitor<Path> fv = new SimpleFileVisitor<Path>() {
      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
          throws IOException {
        System.out.println(file);
        return FileVisitResult.CONTINUE;
      }
    };

    try {
      Files.walkFileTree(p, fv);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

}

如果您正在使用Java 8,您可以使用流接口和java.nio.file.Files.walk(...)
public class WalkFileTreeExample {

  public static void main(String[] args) {
    try (Stream<Path> paths = Files.walk(Paths.get("/usr"))) {
      paths.forEach(System.out::println);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

}

2
使用流是否有一种方法,在遍历新目录时设置检查点并执行函数? - Raghu DV
Files.walk(somePath) 将遍历从根目录可访问的整个文件和目录树。如果您只需要处理特定的文件,例如按其扩展名分类,则可以使用 filterPathMatcher,将其配置为 glob:**.xml,即在后续管道阶段中省略任何非 XML 文件。 - Roman Vottner

28

请查看Apache Commons中的FileUtils类,特别是 iterateFiles

允许对给定目录(以及可选的子目录)中的文件进行迭代。


5
如果您关心内存使用情况,那么这个API实际上并不是真正的流式传输。它首先生成一个集合,然后返回该集合的迭代器:return listFiles(directory, fileFilter, dirFilter).iterator(); - Gili Nachum
Java 1.6的好选择。 - David I.
同意 @GiliNachum 的观点。Apache的FileUtils首先会收集所有文件并为它们提供一个迭代器。如果你有大量的文件,这对资源是有害的。 - Bogdan Samondros

9
使用org.apache.commons.io.FileUtils
File file = new File("F:/Lines");       
Collection<File> files = FileUtils.listFiles(file, null, true);     
for(File file2 : files){
    System.out.println(file2.getName());            
} 

如果您不想获取子目录中的文件,请使用false。


8

对于Java 7+,还有https://docs.oracle.com/javase/7/docs/api/java/nio/file/DirectoryStream.html

以下示例摘自Javadoc:

List<Path> listSourceFiles(Path dir) throws IOException {
   List<Path> result = new ArrayList<>();
   try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.{c,h,cpp,hpp,java}")) {
       for (Path entry: stream) {
           result.add(entry);
       }
   } catch (DirectoryIteratorException ex) {
       // I/O error encounted during the iteration, the cause is an IOException
       throw ex.getCause();
   }
   return result;
}

3

这是一棵树形结构,所以递归是您的好帮手:从父目录开始调用方法来获取子文件数组。遍历子数组。如果当前值是一个目录,将其传递给方法的递归调用。如果不是,则适当地处理叶子文件。


2

正如提到的那样,这是一个递归问题。具体来说,您可能想看一下:

listFiles() 

在Java文件API中(这里),它返回目录中所有文件的数组。结合使用此功能和其他技术,可以实现许多功能,如遍历目录,查找特定文件,筛选文件等。
isDirectory()

查看是否需要进一步递归是一个很好的起点。


这个链接可能会有用,因为答案中的链接已经失效了。 - Donglecow

1

您也可以误用File.list(FilenameFilter)(和变体)进行文件遍历。 这是一段简短的代码,适用于早期的Java版本,例如:

// list files in dir
new File(dir).list(new FilenameFilter() {
    public boolean accept(File dir, String name) {
        String file = dir.getAbsolutePath() + File.separator + name;
        System.out.println(file);
        return false;
    }
});

0
补充@msandiford的答案,当遍历文件树时,大多数情况下您可能希望在访问目录或任何特定文件时执行函数。如果您不想使用流,则可以实现以下重写方法。
Files.walkFileTree(Paths.get(Krawl.INDEXPATH), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
    new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException {
                // Do someting before directory visit
                return FileVisitResult.CONTINUE;
        }
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException {
                // Do something when a file is visited
                return FileVisitResult.CONTINUE;
        }
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                throws IOException {
                // Do Something after directory visit 
                return FileVisitResult.CONTINUE;
        }
});

0

我喜欢使用Optionalstreams来获得一个简洁明了的解决方案,我使用以下代码来迭代一个目录。以下情况由代码处理:

  1. 处理空目录的情况
  2. 懒加载

但正如其他人所提到的,如果你有大量文件夹,仍然需要注意内存溢出的问题。

    File directoryFile = new File("put your path here");
    Stream<File> files = Optional.ofNullable(directoryFile// directoryFile
                                                          .listFiles(File::isDirectory)) // filter only directories(change with null if you don't need to filter)
                                 .stream()
                                 .flatMap(Arrays::stream);// flatmap from Stream<File[]> to Stream<File>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接