目录遍历与文件过滤
你有没有踩过这个坑:
遍历目录时,空目录抛了 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(),不要用完整路径 │
└─────────────────────────────────────────────────────────────────┘