高效复制的关键是选对通道类型、缓冲策略并避开退化路径:首选transferTo实现零拷贝(需源/目标均为本地FileChannel且非FileOutputStream.getChannel()),次选手动DirectByteBuffer循环(注意flip/clear),大文件随机访问可用内存映射;忌用FileInputStream/FileOutputStream.getChannel()、跨文件系统、堆内Buffer及误调flip/compact。

高效复制的关键不是“用通道”,而是用对通道类型、配合正确的缓冲策略,并避开常见退化路径。真正能跑出零拷贝性能的,只在特定组合下成立。
首选 transferTo 实现内核级搬运
这是最简、最快的方式,但必须同时满足三个硬性条件:
- 源通道必须是本地可读的 FileChannel(不能是
FileInputStream.getChannel()) - 目标通道必须是同文件系统下的可写 FileChannel(跨磁盘或 NFS 会退化)
- 目标通道不能来自
FileOutputStream.getChannel()(它不支持接收 transferTo 数据)
✅ 正确写法:用 RandomAccessFile 获取双向通道:
try (RandomAccessFile rafIn = new RandomAccessFile("src.bin", "r");
RandomAccessFile rafOut = new RandomAccessFile("dst.bin", "rw")) {
FileChannel in = rafIn.getChannel();
FileChannel out = rafOut.getChannel();
long size = in.size();
in.transferTo(0, size, out); // 一行完成,无 JVM 内存参与
}手动 ByteBuffer 循环读写(可控但需精细管理)
当需要加解密、进度监控、压缩,或目标不满足零拷贝条件时,此方式更稳妥。重点在缓冲区选择与状态流转:
立即学习“Java免费学习笔记(深入)”;
- 用
ByteBuffer.allocateDirect(64 * 1024),避免堆内 buffer 引发的隐式拷贝 -
read()后必须调用flip(),否则write()写入 0 字节 -
write()可能只写部分数据,需用while (buf.hasRemaining())循环确保写出 - 每轮结束用
clear(),不要用compact()(除非保留未读完内容)
ByteBuffer buf = ByteBuffer.allocateDirect(65536);
while (source.read(buf) != -1) {
buf.flip();
while (buf.hasRemaining()) {
target.write(buf);
}
buf.clear();
}内存映射适合大文件随机访问
用 FileChannel.map() 将文件区域直接映射为 MappedByteBuffer,绕过传统 I/O 路径:
- 适用于日志分析、数据库索引等需频繁跳转读写的场景
- 映射后可像操作内存一样读写文件任意位置,如
buf.putInt(0, 123) - 调用
force()确保修改刷盘(如需强持久化) - ⚠️ 风险:外部程序修改文件可能触发
IOException;长期映射超大文件易引发 OOM
try (FileChannel ch = FileChannel.open(path, READ, WRITE)) {
MappedByteBuffer buf = ch.map(READ_WRITE, 0, ch.size());
buf.putInt(0, 123);
buf.force();
}别踩这些坑
很多“用了 FileChannel 却更慢”的案例,根源在于误配:
- 用
FileInputStream.getChannel()+FileOutputStream.getChannel()组合 —— JDK 8–17 多数静默退化为用户态循环读写 - 跨文件系统复制(如 /dev/sda1 → /dev/sdb1)—— Linux 下无法触发
copy_file_range - 用堆内 buffer(
allocate())替代 direct buffer —— 多一次 JVM 堆拷贝 - 忘记
flip()或误用compact()—— 导致数据丢失或死循环


















