1. 概述

本文将介绍几种使用 Java 遍历文件夹中所有文件的方法,包括遍历当前目录和子文件夹的方法。

2. 使用 listFiles

我们可以通过 java.io.File对象的listFiles() 方法来遍历指定目录下的所有文件:

public Set<String> listFilesUsingJavaIO(String dir) {
    return Stream.of(new File(dir).listFiles())
      .filter(file -> !file.isDirectory())
      .map(File::getName)
      .collect(Collectors.toSet());
}

listFiles() 返回的是一个 File 对象数组。然后我们使用Java8 Stream来进行遍历,并滤掉所有子文件夹,最终结果收集到Set中。

使用 listFiles() 方法时要注意空指针问题,如果提供的目录无效时它会抛出NullPointerException

assertThrows(NullPointerException.class,
        () -> listFiles.listFilesUsingJavaIO(INVALID_DIRECTORY));

使用 listFiles() 的一个缺点是它会一次读取整个目录,因此对于包含文件数量较多的文件夹来说可能存在性能问题。

下面我们来讨论其他替代方案。

3. 使用 DirectoryStream

Java 7 引入了文件夹流 —— DirectoryStream 可用于替代 listFiles。DirectoryStream 可以很好地与for-each 结合以实现文件夹遍历,而不用一次性读取整个内容。

下面我们使用 DirectoryStream 来列出目录中的文件:

public Set<String> listFilesUsingDirectoryStream(String dir) throws IOException {
    Set<String> fileSet = new HashSet<>();
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(dir))) {
        for (Path path : stream) {
            if (!Files.isDirectory(path)) {
                fileSet.add(path.getFileName()
                    .toString());
            }
        }
    }
    return fileSet;
}

同样,我们返回了文件夹中的文件列表,并过滤掉了子文件夹。我们还通过 try-with-resources 自动处理 DirectoryStream 资源的关闭。

从名字上会让人误以为 DirectoryStream 属于 Stream API,其实不是的。下面,我们学习如何使用Stream API来列出文件。

4. 使用 Files.list

Java 8 在 java.nio.file.Files 中引入了新的 list() 方法。

4.1 使用 Files.list()

下面是一个简单的例子:

public Set<String> listFilesUsingFilesList(String dir) throws IOException {
    try (Stream<Path> stream = Files.list(Paths.get(dir))) {
        return stream
          .filter(file -> !Files.isDirectory(file))
          .map(Path::getFileName)
          .map(Path::toString)
          .collect(Collectors.toSet());
    }
}

这段代码功能和前面一样,虽然这看起来与 listFiles() 类似,但在获取文件路径的方式上有所不同。

在这里,list() 方法返回了一个 Stream 对象,它惰性地填充了目录条目的内容。因此,我们可以更有效地处理大型文件夹。

4.2 与 File.list() 比较

我们不要混淆Files类提供的list()方法与File提供的list()方法。后者返回的是数组类型。

5. 使用 Files.walk遍历目录树

除了列出当前目录下的文件,有时我们还想递归访问子文件夹,进行深度遍历。这种情况下,我们可以使用walk()方法:

public Set<String> listFilesUsingFileWalk(String dir, int depth) throws IOException {
    try (Stream<Path> stream = Files.walk(Paths.get(dir), depth)) {
        return stream
          .filter(file -> !Files.isDirectory(file))
          .map(Path::getFileName)
          .map(Path::toString)
          .collect(Collectors.toSet());
    }
}

我们使用 walk() 深度遍历目录树,并将所有文件的名称收集到了一个Set中。

此外,如果我们需要在遍历时对文件执行一些操作,我们可以使用walkFileTree():

public Set<String> listFilesUsingFileWalkAndVisitor(String dir) throws IOException {
    Set<String> fileList = new HashSet<>();
    Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            if (!Files.isDirectory(file)) {
                fileList.add(file.getFileName().toString());
            }
            return FileVisitResult.CONTINUE;
        }
    });
    return fileList;
}

这个方法在我们需要进行额外的读取、移动或删除文件时非常有用。

如果给walk()walkFileTree()传的是文件路径不是文件夹不会抛出NullPointerException。实际上,Stream保证至少返回一个元素,即提供的文件本身:

Set<String> expectedFileSet = Collections.singleton("test.xml");
String filePathString = "src/test/resources/listFilesUnitTestFolder/test.xml";
assertEquals(expectedFileSet, listFiles.listFilesUsingFileWalk(filePathString, DEPTH));

6. 结论

在这篇简短的文章中,我们探讨了几种在目录中列出文件的方法。

首先,我们使用listFiles()获取了文件夹的所有内容。然后我们使用DirectoryStream以懒加载方式加载目录内容。我们也使用了Java 8引入的list()方法。

最后,我们演示了walk()walkFileTree()方法用于处理文件树。

如常,示例的完整源代码可以在GitHub仓库中找到。