FileChannel.transferTo在Linux+SocketChannel下可实现零拷贝:数据从磁盘经内核缓冲区直传网卡DMA,跳过JVM堆内存,仅2次拷贝和2次上下文切换;需满足源为只读FileChannel、目标为已连接SocketChannel、OS支持sendfile、单次≤2GB且循环校验返回值。

Java NIO 中用 FileChannel.transferTo 传输数据,本质是让操作系统内核直接搬运数据,跳过 JVM 堆内存,从而减少拷贝次数和上下文切换。它不是“一调就完”,而是要满足条件、控制范围、循环校验才能真正高效。
必须满足的几个硬性前提
transferTo 要发挥零拷贝效果,这几条缺一不可:
- 源通道必须是 FileChannel,且以只读方式打开(如
StandardOpenOption.READ) - 目标通道要是 WritableByteChannel 的具体实现,常见的是
SocketChannel(网络发送)或另一个FileChannel(本地文件复制) - 操作系统得支持底层零拷贝机制:Linux ✅(用
sendfile)、macOS 部分支持、Windows ❌(会自动退化为普通 read/write 拷贝) - 文件不能被其他进程独占锁定,否则可能抛
IOException - 单次调用最多传 2GB − 1 字节(即
Integer.MAX_VALUE),超长必须分段
典型安全写法:位置 + 长度 + 循环校验
别指望一次调用传完全部数据。正确做法是维护当前偏移量,每次传不超过 2GB,并检查返回值是否真实传输了字节数:
(以服务端向客户端发送大文件为例)
立即学习“Java免费学习笔记(深入)”;
try (FileChannel src = FileChannel.open(Paths.get("bigfile.zip"), StandardOpenOption.READ);
SocketChannel dst = clientSocketChannel) {
<pre class="brush:php;toolbar:false;">long pos = 0;
long size = src.size();
while (pos < size) {
long count = Math.min(size - pos, Integer.MAX_VALUE);
long transferred = src.transferTo(pos, count, dst);
if (transferred == 0) {
// 目标暂时不可写(如 TCP 窗口满、非阻塞模式下),可稍作等待或交由 selector 处理
continue;
}
pos += transferred;
}}
不同场景下的目标通道选择
目标类型决定能否走零拷贝路径:
-
发给网络客户端:用已连接的
SocketChannel(推荐设为阻塞模式,避免反复返回 0) -
复制到另一个文件:用
FileOutputStream.getChannel()获取目标FileChannel;Linux 2.6.32+ 支持 sendfile64,可零拷贝;旧版或跨文件系统时可能退化 -
不能用的情况:目标是
PipeChannel、未连接的SocketChannel、关闭中的通道,或 Windows 下任意目标——这些都会回退到用户态拷贝
为什么比传统方式快?关键在数据路径压缩
对比传统 FileInputStream → byte[] → OutputStream:
- 传统路径:磁盘 → 内核缓冲区 → JVM 堆 → 内核 socket 缓冲区 → 网卡(4 次拷贝 + 4 次上下文切换)
- transferTo 路径(Linux + SocketChannel):磁盘 → 内核缓冲区 → 网卡 DMA(2 次拷贝 + 2 次上下文切换,CPU 不搬数据)
实际吞吐提升明显,尤其适合视频流、大附件下载等高带宽场景。


















