1. 简介

本文将介绍几种使用Java核心API和第三方库在指定目录(包括子目录)中查找特定扩展名文件的方法。我们将从传统的数组和列表方式,逐步过渡到流式处理等现代技术。

2. 构建文件过滤器

首先需要实现一个扩展名匹配的过滤器。这里我们使用Predicate接口,并处理一些边界情况(如扩展名是否带点号):

public class MatchExtensionPredicate implements Predicate<Path> {

    private final String extension;

    public MatchExtensionPredicate(String extension) {
        if (!extension.startsWith(".")) {
            extension = "." + extension;
        }
        this.extension = extension.toLowerCase();
    }

    @Override
    public boolean test(Path path) {
        if (path == null) {
            return false;
        }
        return path.getFileName()
          .toString()
          .toLowerCase()
          .endsWith(extension);
    }
}

关键点:

  • 构造器自动补全点号并转为小写
  • test()方法获取文件名后统一转为小写比较
  • ✅ 处理了null值和大小写问题

3. 使用Files.listFiles()遍历目录

这是Java早期提供的传统方法,通过递归实现目录遍历:

List<File> find(File startPath, String extension) {
    List<File> matches = new ArrayList<>();

    File[] files = startPath.listFiles();
    if (files == null) {
       return matches;
    }

    MatchExtensionPredicate filter = new MatchExtensionPredicate(extension);
    for (File file : files) {
        if (file.isDirectory()) {
            matches.addAll(find(file, extension));
        } else if (filter.test(file.toPath())) {
            matches.add(file);
        }
    }

    return matches;
}

⚠️ 注意:

  • 需要手动处理递归逻辑
  • 深层目录可能导致StackOverflowError
  • 大量文件可能触发OutOfMemoryError

4. 使用Java 7+的Files.walkFileTree()

Java 7引入的NIO.2 API提供了更优雅的解决方案:

List<Path> find(Path startPath, String extension) throws IOException {
    List<Path> matches = new ArrayList<>();

    Files.walkFileTree(startPath, new SimpleFileVisitor<Path>() {
        
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
            if (new MatchExtensionPredicate(extension).test(file)) {
                matches.add(file);
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            return FileVisitResult.CONTINUE;
        }
    });
    return matches;
}

优势:

  • ✅ 自动处理递归遍历
  • ✅ 通过visitFileFailed优雅处理访问异常
  • ✅ 使用Path替代传统File

5. 使用Java 8+的Files.walk()

Java 8的Stream API提供了更简洁的写法:

Stream<Path> find(Path startPath, String extension) throws IOException {
    return Files.walk(startPath)
      .filter(new MatchExtensionPredicate(extension));
}

⚠️ 缺陷:

  • 遇到第一个异常就会中断
  • 目前无法优雅处理异常(JDK-8039910

改进方案:结合FileVisitorConsumer

public class SimpleFileConsumerVisitor extends SimpleFileVisitor<Path> {

    private final Predicate<Path> filter;
    private final Consumer<Path> consumer;

    public SimpleFileConsumerVisitor(MatchExtensionPredicate filter, Consumer<Path> consumer) {
        this.filter = filter;
        this.consumer = consumer;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
        if (filter.test(file)) {
            consumer.accept(file);
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        return FileVisitResult.CONTINUE;
    }
}

使用方式:

void find(Path startPath, String extension, Consumer<Path> consumer) throws IOException {
    MatchExtensionPredicate filter = new MatchExtensionPredicate(extension);
    Files.walkFileTree(startPath, new SimpleFileConsumerVisitor(filter, consumer));
}

6. 使用Apache Commons IO的FileUtils.iterateFiles()

引入依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.1</version>
</dependency>

实现代码:

Iterator<File> find(Path startPath, String extension) {
    if (!extension.startsWith(".")) {
        extension = "." + extension;
    }
    return FileUtils.iterateFiles(
      startPath.toFile(), 
      WildcardFileFilter.builder().setWildcards("*" + extension).get(), 
      TrueFileFilter.INSTANCE);
}

特点:

  • ✅ 使用通配符过滤器简化匹配逻辑
  • TrueFileFilter.INSTANCE确保递归子目录
  • ⚠️ 需要手动处理PathFile的转换

7. 总结

本文对比了四种文件查找方案:

方法 优点 缺点
listFiles() 简单直接 需手动递归,性能风险
walkFileTree() 异常处理完善 代码稍显冗长
Files.walk() 最简洁流式API 异常处理缺陷
Commons IO 功能强大 需要额外依赖

推荐选择

  • 简单场景:Files.walk()
  • 生产环境:walkFileTree()或Commons IO
  • 避免使用:传统listFiles()递归方式

完整代码示例可在GitHub获取。


原始标题:Find Files by Extension in Specified Directory in Java | Baeldung