Java NIO 文件操作详解:FileChannel、Path 与 Files 工具类
FileChannel 基础操作
FileChannel 是 Java NIO 中用于高效读写文件的通道,仅支持阻塞模式。它不能直接实例化,需通过 FileInputStream、FileOutputStream 或 RandomAccessFile 的 getChannel() 方法获取:
- 从
FileInputStream获取的通道仅可读; - 从
FileOutputStream获取的通道仅可写; RandomAccessFile的通道读写能力取决于其构造时指定的模式(如 "r" 或 "rw")。
读取数据:调用 channel.read(buffer) 将数据填充到 ByteBuffer,返回实际读取的字节数,-1 表示到达文件末尾。
写入数据:需先将数据写入缓冲区,切换为读模式后循环写入,因为单次 write 调用可能无法写完全部内容:
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(data);
buf.flip();
while (buf.hasRemaining()) {
channel.write(buf);
}
位置与大小:可通过 position() 获取或设置当前读写位置。若设为超出文件末尾的位置再写入,中间会产生空洞(以零填充)。使用 size() 可获取文件总字节数。
强制刷盘:调用 force(true) 可确保文件内容及元数据立即写入磁盘,绕过操作系统缓存。
关闭资源:关闭底层流(如 FileInputStream)会自动关闭关联的 FileChannel。
高效文件传输:transferTo 与 transferFrom
利用 transferTo 可在两个 FileChannel 间直接传输数据,底层通常使用零拷贝技术提升性能:
try (FileChannel src = new FileInputStream("src.txt").getChannel();
FileChannel dst = new FileOutputStream("dst.txt").getChannel()) {
long total = src.size();
for (long pos = 0; pos < total; ) {
long transferred = src.transferTo(pos, total - pos, dst);
pos += transferred;
}
}
该方法适用于大文件传输,即使超过 2GB 也能分段处理。
Path 与路径操作
自 JDK 7 起,Path 接口用于表示文件路径,Paths 是其工厂类:
Path p1 = Paths.get("data.txt"); // 相对路径
Path p2 = Paths.get("C:\\data\\file.txt"); // Windows 绝对路径
Path p3 = Paths.get("/home/user/file.txt"); // Unix 绝对路径
Path p4 = Paths.get("data", "sub", "file.txt"); // 自动拼接路径
使用 normalize() 可解析 . 和 ..,得到规范路径:
Path raw = Paths.get("a/../b");
System.out.println(raw.normalize()); // 输出: b
Files 工具类常用方法
Files 提供了丰富的静态方法用于文件系统操作:
- 存在性检查:
Files.exists(path) - 创建目录:
createDirectory(path):仅创建单层目录;createDirectories(path):递归创建多级目录。
- 复制与移动:
Files.copy(src, tgt, StandardCopyOption.REPLACE_EXISTING); Files.move(src, tgt, StandardCopyOption.ATOMIC_MOVE); - 删除操作:
delete(path):删除文件或空目录;- 非空目录需先清空内容再删除。
遍历文件树:使用 walkFileTree 配合 SimpleFileVisitor 可深度遍历目录结构:
Files.walkFileTree(root, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (file.toString().endsWith(".jar")) {
jarCount.incrementAndGet();
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
Files.delete(dir); // 删除空目录
return FileVisitResult.CONTINUE;
}
});
批量复制目录:结合 Files.walk() 流式 API 可实现整个目录的复制:
Files.walk(Paths.get(srcDir))
.forEach(p -> {
Path targetPath = Paths.get(p.toString().replace(srcDir, destDir));
try {
if (Files.isDirectory(p)) {
Files.createDirectories(targetPath);
} else {
Files.copy(p, targetPath);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
注意:此类操作需谨慎处理异常,并确保目标路径不存在冲突。
