作者介绍:大家好,我是 CodeStats。一个在底层技术上“考古”了四年的硬核爱好者,也是 WWAIC(全周项目AI编程)范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架(从 IoC 容器到嵌入式 Tomcat,代码全开源),也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。我的技术信条:所有高深的技术,最后都能用大白话讲清楚。如果讲不清楚,说明还没真正理解。


本文能获得什么

  • 🚀 上手即用:手把手带你掌握 NIO.2 的 PathFilesWatchService 等核心 API,所有代码均可直接复制运行

  • ⚔️ 选型不再纠结:通过源码剖析和场景化对比,彻底搞懂 Files 与 FileChannel 的本质差异,知道什么时候该用谁

  • 📊 性能实测参考:给出不同场景下的性能排序,帮你避免“盲目追求底层”或“过度封装”的误区

  • 🏆 新旧交替解惑:明确 RandomAccessFile 为什么被淘汰,以及 FileChannel 如何完美替代它


目录

  1. NIO.2 核心 API 实战

    • 1.1 Path:现代化路径操作

    • 1.2 Files:一站式文件工具

    • 1.3 目录遍历:list 与 walk

    • 1.4 WatchService:文件变化监听

  2. Files API 与 FileChannel:到底怎么选?

    • 2.1 源码拆解:Files 底层就是 FileChannel

    • 2.2 核心差异对比表

    • 2.3 场景一:一次性写入小文件

    • 2.4 场景二:高频循环写入

    • 2.5 场景三:随机位置写入

  3. FileChannel 与 RandomAccessFile:谁主沉浮?

    • 3.1 历史渊源

    • 3.2 功能对比

    • 3.3 为什么说 RandomAccessFile 已经过时?

  4. 性能实测:谁最快?


一、NIO.2 核心 API 实战

1.1 Path:现代化路径操作

Path 是 NIO.2 中替代 File 的路径抽象:

java

// 创建 Path 的多种方式
Path path1 = Path.of("data", "users.txt");        // Java 11+
Path path2 = Paths.get("data", "users.txt");      // Java 7+

// Path 常用操作
path.getFileName();      // users.txt
path.getParent();        // data
path.toAbsolutePath();   // /full/path/to/data/users.txt
path.resolve("sub");     // data/users.txt/sub

1.2 Files:一站式文件工具

Files 类提供了极其丰富的静态方法,涵盖了文件操作的方方面面:

java

// === 创建 ===
Files.createFile(Path.of("example.txt"));
Files.createDirectory(Path.of("myfolder"));
Files.createDirectories(Path.of("a/b/c/d"));  // 创建多级目录

// === 读写(极简) ===
String content = Files.readString(Path.of("data.txt"));  // 读取整个文件
List<String> lines = Files.readAllLines(Path.of("data.txt"));
byte[] bytes = Files.readAllBytes(Path.of("image.png"));

Files.writeString(Path.of("output.txt"), "Hello, World!");
Files.write(Path.of("output.txt"), List.of("Line 1", "Line 2"));

// === 复制/移动/删除 ===
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.deleteIfExists(Path.of("example.txt"));  // 存在则删除,返回是否成功

// === 检查 ===
boolean exists = Files.exists(Path.of("data.txt"));
boolean isDir = Files.isDirectory(Path.of("data"));
long size = Files.size(Path.of("data.txt"));

1.3 目录遍历:list 与 walk

java

// 列出当前目录的直接内容
try (Stream<Path> stream = Files.list(Path.of("myfolder"))) {
    stream.forEach(System.out::println);
}

// 递归遍历整个目录树
try (Stream<Path> stream = Files.walk(Path.of("myfolder"))) {
    stream.filter(Files::isRegularFile)
          .forEach(System.out::println);
}

1.4 WatchService:文件变化监听

java

Path dir = Path.of("watchdir");
WatchService watchService = FileSystems.getDefault().newWatchService();

dir.register(watchService,
    StandardWatchEventKinds.ENTRY_CREATE,
    StandardWatchEventKinds.ENTRY_DELETE,
    StandardWatchEventKinds.ENTRY_MODIFY);

while (true) {
    WatchKey key = watchService.take();
    for (WatchEvent<?> event : key.pollEvents()) {
        System.out.println("事件: " + event.kind() + " -> " + event.context());
    }
    key.reset();
}

二、Files API 与 FileChannel:到底怎么选?

2.1 源码拆解:Files 底层就是 FileChannel

很多人以为 Files.writeString() 和 FileChannel.write() 是两套独立的体系。实际上,Files 所有方法的底层都在调用 FileChannel(或传统的 FileOutputStream)。

当你调用 Files.writeString(Path.of("data.txt"), "Hello") 时,内部调用链大致是:

text

Files.writeString() 
  -> Files.write() 
    -> Files.newOutputStream() 
      -> FileChannel.open() 
        -> channel.write(ByteBuffer)

本质结论Files 是站在 FileChannel 肩膀上的"语法糖"。它帮你自动打开通道、自动分配缓冲区、自动关闭资源。

2.2 核心差异对比表

对比维度Files API(高级门面)FileChannel(底层工具)
设计理念一次性操作,用完即扔持续性操作,手动控制生命周期
操作次数每次调用都打开→写入→立即关闭打开一次,可反复读写无数次
文件位置控制不支持随机定位(只能从头或追加)通过 position() 随意移动指针
零拷贝能力不支持支持 transferTo() / transferFrom()
文件锁不支持支持 tryLock()
内存映射不支持支持 map() → MappedByteBuffer
资源复用无法复用可持有 Channel 对象重复使用

2.3 场景一:一次性写入小文件

✅ 推荐 Files API

java

// 一行代码,简洁清晰
Files.writeString(Path.of("config.json"), "{\"key\":\"value\"}");

小文件场景下,Files API 的简洁性远超 FileChannel,性能差异可以忽略不计。

2.4 场景二:高频循环写入

❌ 反模式(Files API 每次循环都开关文件)

java

for (int i = 0; i < 100000; i++) {
    Files.writeString(Path.of("log.txt"), i + "\n", StandardOpenOption.APPEND);
    // 每次循环都打开和关闭文件,性能暴跌!
}

✅ 正确姿势(FileChannel 复用通道)

java

try (FileChannel channel = FileChannel.open(Paths.get("log.txt"),
        StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    for (int i = 0; i < 100000; i++) {
        buffer.clear();
        buffer.put((i + "\n").getBytes());
        buffer.flip();
        channel.write(buffer);  // 复用同一个通道,性能提升百倍
    }
}

实验数据表明,FileChannel 的写入性能在各种文件大小下通常表现最佳。

2.5 场景三:随机位置写入

❌ Files API 无法实现——它不支持 position() 定位。

✅ FileChannel 轻松应对

java

try (FileChannel channel = FileChannel.open(Paths.get("data.bin"),
        StandardOpenOption.READ, StandardOpenOption.WRITE)) {
    ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());
    channel.position(1024L);  // 跳到第 1024 字节处写入
    channel.write(buffer);
}

三、FileChannel 与 RandomAccessFile:谁主沉浮?

3.1 历史渊源

RandomAccessFile 早在 NIO 出现之前就已经存在了。它支持在文件的任意位置进行读写,是传统 IO 中唯一支持随机访问的类。

FileChannel 是 NIO 引入的通道抽象,同样支持随机访问。

3.2 功能对比

功能RandomAccessFileFileChannel
随机读写✅(实现 SeekableByteChannel
零拷贝✅(transferTo/transferFrom
文件锁✅(tryLock
内存映射✅(map → MappedByteBuffer
聚集/分散读写✅(ScatteringByteChannel/GatheringByteChannel
可中断✅(InterruptibleChannel

3.3 为什么说 RandomAccessFile 已经过时?

从 Java 7 开始,通过 RandomAccessFile 获取 FileChannel 的方式已经被标记为过时

正确的做法是直接使用 FileChannel.open()

java

// 过时的方式(不推荐)
RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
FileChannel channel = raf.getChannel();

// 现代方式(推荐)
FileChannel channel = FileChannel.open(Paths.get("data.txt"),
        StandardOpenOption.READ, StandardOpenOption.WRITE);

结论FileChannel 在功能上已经完全覆盖并超越了 RandomAccessFile。新项目应该直接使用 FileChannel


四、性能实测:谁最快?

根据多方性能测试数据,各种写入方式的时延从小到大排序为:

FileChannel < BufferedOutputStream < FileOutputStream < BufferedWriter < FileWriter

但要注意:小文件场景下(1MB 左右),FileChannel 的优势并不明显,反而可能因为 API 复杂度而得不偿失

选型建议

  • 小文件、一次性操作 → Files API(简洁优先)

  • 大文件、高频操作 → FileChannel(性能优先)

  • 随机读写、需要文件锁 → FileChannel(功能优先)


小结:NIO.2 的 Files 类让日常文件操作变得极其简单,而 FileChannel 则在需要精细控制时提供强大的底层能力。两者不是替代关系,而是不同层次的工具——Files 是"一键启动",FileChannel 是"手动驾驶"。


👍 点赞 | ⭐ 收藏 | 🔔 关注,不错过后续精彩内容!

(下篇预告:零拷贝与 mmap 深度解析、AsynchronousFileChannel 异步操作、FileChannel 与 Socket 的本质区别)

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐