Skip to content

目录遍历与文件过滤

你有没有踩过这个坑:

遍历目录时,空目录抛了 NullPointerException。

或者这个:

用正则过滤文件名,结果正则写错了,所有文件都没匹配上。

这些都是目录遍历时常见的坑。

这个过滤坑我踩过

当年写一个文件清理工具,需要找出所有 .tmp 文件。代码写的是:

java
File[] files = dir.listFiles(f -> 
    f.getName().matches(".*\\.tmp"));

测试通过,上线后发现:有些 .tmp 文件没被找到。

排查后发现:某些文件的完整路径是 /path/to/.tmp/file.tmp,但 getName() 只返回 file.tmp,而正则 .*\\.tmp 匹配的是整个路径字符串。

正确的写法应该用 getName() 而不是直接用路径。

File 遍历

java
File dir = new File("mydir");

// 列出文件名
String[] names = dir.list();

// 列出 File 对象
File[] files = dir.listFiles();

// 带过滤器
File[] txtFiles = dir.listFiles((d, name) -> name.endsWith(".txt"));

Files 遍历

java
// list:只遍历一层子项
try (Stream<Path> stream = Files.list(Path.of("mydir"))) {
    stream.forEach(System.out::println);
}

// walk:深度优先遍历
try (Stream<Path> stream = Files.walk(Path.of("mydir"))) {
    stream.forEach(System.out::println);
}

// walk 时跳过根目录
try (Stream<Path> stream = Files.walk(Path.of("mydir"), 2)) {
    stream.skip(1).forEach(System.out::println);
}

文件过滤器

按扩展名过滤

java
// File + FilenameFilter
File[] txts = dir.listFiles((d, name) -> name.endsWith(".txt"));

// Files + lambda
try (Stream<Path> stream = Files.list(Path.of("mydir"))) {
    stream.filter(p -> p.getFileName().toString().endsWith(".txt"))
          .forEach(System.out::println);
}

按大小过滤

java
// File
File[] largeFiles = dir.listFiles(f ->
    f.isFile() && f.length() > 1024 * 1024);

// Files
try (Stream<Path> stream = Files.list(Path.of("mydir"))) {
    stream.filter(p -> Files.isRegularFile(p) && Files.size(p) > 1024)
          .forEach(System.out::println);
}

按修改时间过滤

java
long oneDayAgo = System.currentTimeMillis() - 24 * 60 * 60 * 1000L;

// File
File[] recentFiles = dir.listFiles(f ->
    f.lastModified() > oneDayAgo);

// Files
try (Stream<Path> stream = Files.list(Path.of("mydir"))) {
    stream.filter(p -> {
        try {
            return Files.getLastModifiedTime(p).toMillis() > oneDayAgo;
        } catch (IOException e) {
            return false;
        }
    }).forEach(System.out::println);
}

递归过滤

java
// Files.find() 递归过滤
try (Stream<Path> stream = Files.find(Path.of("mydir"),
        Integer.MAX_VALUE,  // 最大深度
        (p, attrs) -> {
            if (!attrs.isRegularFile()) return false;
            return p.getFileName().toString().endsWith(".java") && attrs.size() > 0;
        })) {
    stream.forEach(System.out::println);
}

统计文件信息

java
public static void printFileStats(Path dir) {
    try (Stream<Path> stream = Files.walk(dir)) {
        Map<String, Long> extCount = stream
            .filter(Files::isRegularFile)
            .collect(Collectors.groupingBy(
                p -> {
                    String name = p.getFileName().toString();
                    int idx = name.lastIndexOf('.');
                    return idx > 0 ? name.substring(idx) : "(无扩展名)";
                },
                Collectors.counting()
            ));
        extCount.forEach((ext, count) ->
            System.out.println(ext + ": " + count + " 个文件"));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

查找文件

java
// 递归查找特定文件
public static List<Path> findFiles(Path dir, String filename) {
    List<Path> result = new ArrayList<>();
    try (Stream<Path> stream = Files.walk(dir)) {
        stream.filter(p -> p.getFileName().toString().equals(filename))
              .forEach(result::add);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}

// 使用
List<Path> configFiles = findFiles(Path.of("."), "config.yaml");

常见问题

list() / listFiles() 返回 null

java
// ❌ 不检查 null
File[] files = dir.listFiles();
for (File f : files) { // 空目录或权限不足时 null,抛 NPE
}

// ✅ 检查 null
File[] files = dir.listFiles();
if (files != null) {
    for (File f : files) {
        // 处理
    }
}

总结

┌─────────────────────────────────────────────────────────────────┐
│  目录遍历注意点:                                                 │
│                                                                 │
│  1. listFiles() 可能返回 null,必须检查                          │
│  2. 用 getName() 而不是路径字符串做文件名过滤                    │
│  3. Files.walk() 返回的 Stream 必须关闭                          │
│  4. 正则过滤用 getFileName(),不要用完整路径                      │
└─────────────────────────────────────────────────────────────────┘

基于 VitePress 构建