NIO.2 与文件操作
约 899 字大约 3 分钟
布欧-Lewyon
2026-05-15
Java 7 引入的 NIO.2(New I/O 2)以 Path 和 Files 为核心,提供了比传统 java.io.File 更简洁、更安全的文件操作 API。
Path vs 传统 File
Path 是 NIO.2 中表示文件/目录路径的接口,不可变且线程安全:
// 创建 Path
Path p1 = Path.of("/home/user/docs/readme.txt"); // Java 11+
Path p2 = Paths.get("docs", "readme.txt"); // 可拼接多段
Path p3 = Path.of(".").toAbsolutePath().normalize(); // 当前目录的绝对路径
// 常用方法
System.out.println(p1.getFileName()); // readme.txt
System.out.println(p1.getParent()); // /home/user/docs
System.out.println(p1.getRoot()); // /
System.out.println(p1.resolve("backup")); // /home/user/docs/readme.txt/backup
// 与传统 File 互转
File file = p1.toFile();
Path back = file.toPath();Path.of是 Java 11 的静态工厂方法;Paths.get在旧版本中更常见,功能相同。resolve拼接路径,遇到绝对参数字符串时直接替换当前路径(不会重复拼接)。
Files 工具类
Files 封装了几乎所有常见的文件操作,改进了传统 File 方法返回 boolean 但不说原因的问题——异常时抛出 IOException 附带错误原因。
读写文件
// 读取全部内容到字符串(Java 11+)
String content = Files.readString(Path.of("input.txt"), StandardCharsets.UTF_8);
// 按行读取
List<String> lines = Files.readAllLines(Path.of("input.txt"), StandardCharsets.UTF_8);
// 写入字符串(覆盖)
Files.writeString(Path.of("output.txt"), "Hello, NIO.2", StandardCharsets.UTF_8);
// 写入多行
Files.write(Path.of("output.txt"), List.of("行1", "行2"), StandardCharsets.UTF_8);- Java 11 的
readString/writeString最简洁;Java 8 可用readAllLines和write。 - 始终指定
StandardCharsets.UTF_8避免平台编码差异。
复制、移动、删除
Path src = Path.of("source.txt");
Path dst = Path.of("backup/source.txt");
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
Files.move(src, Path.of("archive/source.txt"), StandardCopyOption.ATOMIC_MOVE);
Files.delete(dst); // 不存在则抛 NoSuchFileException
boolean ok = Files.deleteIfExists(dst); // 不存在返回 false,不抛异常REPLACE_EXISTING:覆盖已存在的目标文件。ATOMIC_MOVE:保证移动操作在文件系统层面是原子的(支持的文件系统上)。Files.createDirectories可递归创建目录,类似mkdir -p。
检查与元数据
boolean exists = Files.exists(path);
boolean isDir = Files.isDirectory(path);
boolean isFile = Files.isRegularFile(path);
long size = Files.size(path); // 字节数
FileTime lastModified = Files.getLastModifiedTime(path);目录遍历
// 列出当前目录(非递归)
try (Stream<Path> stream = Files.list(Path.of("."))) {
stream.forEach(System.out::println);
}
// 递归遍历
try (Stream<Path> stream = Files.walk(Path.of("src"))) {
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
// 遍历并访问 FileVisitor 接口(更底层的控制)
Files.walkFileTree(Path.of("src"), new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println(file);
return FileVisitResult.CONTINUE;
}
});Files.list和Files.walk返回Stream<Path>,使用后必须关闭(try-with-resources),否则文件描述符泄漏。Files.walk默认深度Integer.MAX_VALUE,可通过重载walk(path, maxDepth)限制深度。
传统 File 的局限与迁移建议
| 对比维度 | java.io.File | java.nio.file.Path / Files |
|---|---|---|
| 不可变性 | 可变(renameTo 改变自身) | Path 不可变 |
| 异常信息 | 返回 boolean,不说明失败原因 | 抛出 IOException,携带错误描述 |
| 符号链接 | 有限支持 | 完整支持 |
| 文件系统 | 单一 | 支持多种 FileSystem |
| 目录遍历 | listFiles() 笨重 | Files.walk / Files.find 更灵活 |
新代码应优先使用 NIO.2 API,仅在对接遗留接口时通过 path.toFile() 转换。
小结
Path是不可变的路径抽象,代替传统的File;用Path.of或Paths.get创建。Files工具类提供 read/write/copy/move/delete 等操作,异常时抛出IOException。- 目录遍历用
Files.list(单层)和Files.walk(递归),返回的Stream必须关闭。 - 小文件可用
Files.readString/Files.writeString(Java 11+)一行搞定读写。 - 易错:
Files.list和Files.walk返回的Stream持有底层目录流,必须在try-with-resources中使用,否则文件描述符泄漏可能导致"打开文件过多"错误。 - 思考任务:用
Files.walk递归遍历一个项目目录,统计其中所有.java文件的总行数。
上一节:字符集与编码
下一节:JDBC 基础
