378. Java IO API - 遍历文件树

17 阅读2分钟

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());
    }
}

🧪 使用场景示例

  1. 删除 .class 文件

    if (file.toString().endsWith(".class")) {
        Files.delete(file);
    }
    
  2. 查找一年未访问的文件

    FileTime lastAccessTime = (FileTime) Files.getAttribute(file, "lastAccessTime");
    if (lastAccessTime.toInstant().isBefore(Instant.now().minus(365, ChronoUnit.DAYS))) {
        System.out.println("Old file: " + file);
    }
    
  3. 记录遍历日志并写入文件: 在 visitFilepostVisitDirectory 中写入日志内容到 log.txt


🎯 补充建议

  • 遍历文件树是深度优先的。
  • 可以使用 EnumSet.of(FileVisitOption.FOLLOW_LINKS) 作为参数,开启对符号链接的支持。
  • 异常处理非常关键,不要中断整个遍历过程,除非你确实需要这么做。