真正高效的FileChannel文件复制必须满足前提:源为本地可读FileChannel(禁用FileInputStream)、目标为同文件系统可写FileChannel(禁用FileOutputStream),推荐RandomAccessFile获取双向通道调用transferTo实现零拷贝;手动ByteBuffer循环需用direct buffer、严格flip/clear管理状态;内存映射适用于大文件随机访问。

FileChannel 配合 Buffer 实现高效文件复制,关键不在“能用”,而在“用对”——尤其是避免常见误区导致性能反降。真正高效的路径有明确前提和操作规范。
用 transferTo 实现零拷贝复制(推荐首选)
这是最简、最快的方式,但必须满足三个硬性条件才能触发底层零拷贝(如 Linux 的 copy_file_range 或 sendfile):
- 源通道必须是本地文件的可读 FileChannel(不能是 FileInputStream.getChannel())
- 目标通道必须是可写的 FileChannel,且与源同属一个文件系统(跨磁盘或 NFS 会退化)
- 目标通道不能是 FileOutputStream.getChannel() —— 它返回的通道不支持接收 transferTo 数据
✅ 正确写法:用 RandomAccessFile 获取双向通道
Live Stream Script 复制AI写代码
立即学习“Java免费学习笔记(深入)”;
RandomAccessFile rafOut = new RandomAccessFile("dst.bin", "rw")) {
FileChannel in = rafIn.getChannel();
FileChannel out = rafOut.getChannel();
long size = in.size();
in.transferTo(0, size, out); // 一行完成,内核级搬运
}
手动 ByteBuffer 循环读写(可控性强)
当需要监控进度、加解密、压缩,或目标不满足零拷贝条件时,此方式更稳妥。重点在缓冲区选择与状态管理:
- 优先用 ByteBuffer.allocateDirect(64 * 1024)(64KB 是常见平衡点),避免堆内 buffer 的隐式内存拷贝
- 每次
read()后必须调用buffer.flip(),否则write()会写入 0 字节 - 每次
write()后检查返回值,用 while 循环确保全部写出(write()可能只写部分) - 一轮读写结束用
buffer.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 路径:
- 适合大文件且需多次随机读写的场景(如日志分析、数据库页缓存)
- 映射后修改 buffer 会实时同步到文件(需调用
force()强制刷盘) - 风险:映射期间文件被外部截断或删除,会抛 IOException;长期映射超大文件可能引发 OOM
基本用法:
try (RandomAccessFile raf = new RandomAccessFile("data.bin", "rw")) {FileChannel ch = raf.getChannel();
MappedByteBuffer buf = ch.map(READ_WRITE, 0, ch.size());
buf.putInt(0, 123); // 直接写入文件开头
buf.force(); // 确保落盘
}
哪些做法要避免
这些看似合理,实则埋雷的操作会显著拖慢速度甚至出错:
- 用
FileInputStream.getChannel().transferTo(..., FileOutputStream.getChannel())—— JDK 8–17 会静默退化为用户态循环读写,比Files.copy()还慢 - 在循环里反复调用
ByteBuffer.allocateDirect()—— 堆外内存不被 GC 管理,极易内存泄漏 -
read()后忘记flip(),或write()后没处理 partial write —— 导致静默丢数据 - 用
allocate(8192)(堆内 buffer)做高频大文件传输 —— 每次 write 都触发 Unsafe.copyMemory 额外拷贝


















