378. Java IO API - 遍历文件树
你是否需要构建一个应用,用来递归地遍历整个目录树?比如:
- 🧹 删除所有
.class文件? - 🕵️♀️ 查找最近一年未访问的文件?
- 📁 统计某个目录下所有文件的大小?
Java NIO 提供了一个非常强大的机制 —— FileVisitor 接口,配合 Files.walkFileTree() 方法,可以高效优雅地完成这些操作。
🔧 FileVisitor 接口简介
FileVisitor<T> 接口定义了在遍历文件树时的行为回调点:
| 方法 | 调用时机 | 用途示例 |
|---|---|---|
preVisitDirectory(Path dir, BasicFileAttributes attrs) | 正在准备访问目录前 | 打印或跳过目录 |
visitFile(Path file, BasicFileAttributes attrs) | 访问普通文件时 | 删除、拷贝或分析文件 |
visitFileFailed(Path file, IOException exc) | 无法访问文件时 | 记录错误,继续遍历 |
postVisitDirectory(Path dir, IOException exc) | 访问完目录后 | 释放资源、清理操作 |
📌 注意:这个接口类似一个“钩子系统”,允许你在遍历每个文件或目录前后插入自己的逻辑。
🧰 更简单的选择:SimpleFileVisitor
如果你只对其中几个方法感兴趣,可以继承 SimpleFileVisitor 类,它已经实现了默认逻辑,你只需重写你关心的方法,省去样板代码。
📦 示例:打印文件树中的所有文件及其大小
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.*;
import static java.nio.file.FileVisitResult.*;
public class FileTreeWalker {
public static class PrintFiles extends SimpleFileVisitor<Path> {
// 每当访问一个文件时调用
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
if (attr.isSymbolicLink()) {
System.out.format("🔗 Symbolic link: %s", file);
} else if (attr.isRegularFile()) {
System.out.format("📄 Regular file: %s", file);
} else {
System.out.format("❓ Other: %s", file);
}
System.out.println(" (" + attr.size() + " bytes)");
return CONTINUE;
}
// 每当访问完一个目录后调用
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
System.out.format("📁 Directory: %s%n", dir);
return CONTINUE;
}
// 如果文件访问失败
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.err.format("⚠️ Cannot access file: %s (%s)%n", file, exc);
return CONTINUE; // 忽略错误继续
}
}
public static void main(String[] args) throws IOException {
Path startingDir = Paths.get("your/start/path"); // 修改为你自己的路径
Files.walkFileTree(startingDir, new PrintFiles());
}
}
🧪 使用场景示例
-
删除
.class文件:if (file.toString().endsWith(".class")) { Files.delete(file); } -
查找一年未访问的文件:
FileTime lastAccessTime = (FileTime) Files.getAttribute(file, "lastAccessTime"); if (lastAccessTime.toInstant().isBefore(Instant.now().minus(365, ChronoUnit.DAYS))) { System.out.println("Old file: " + file); } -
记录遍历日志并写入文件: 在
visitFile和postVisitDirectory中写入日志内容到log.txt。
🎯 补充建议
- 遍历文件树是深度优先的。
- 可以使用
EnumSet.of(FileVisitOption.FOLLOW_LINKS)作为参数,开启对符号链接的支持。 - 异常处理非常关键,不要中断整个遍历过程,除非你确实需要这么做。