files.copy有时比filechannel.transferto慢,因其默认用缓冲数组循环读写,不强制零拷贝;而transferto在linux/unix上可触发内核dma直传,但仅大文件(>1mb)、源或目标为filechannel且文件系统支持时才明显更快。

Files.copy 为什么有时比通道慢?
因为 Files.copy 默认使用“缓冲字节数组 + 循环读写”,底层没强制走零拷贝;而 FileChannel.transferTo 在支持的系统(Linux/Unix)上能触发内核态直接 DMA 传输,跳过用户态内存拷贝。
但这个优势只在大文件(通常 >1MB)、源/目标至少一方是 FileChannel 且底层文件系统支持时才明显。小文件反而因 JNI 开销略输。
-
Files.copy更安全:自动处理符号链接、权限继承(取决于CopyOption),跨文件系统也稳 -
transferTo可能失败:遇到 NFS、某些容器挂载卷或 FAT32 会退化为普通复制,且不处理 ACL 或 xattr - Windows 上
transferTo不支持从FileInputStream.getChannel()向文件写入(会抛IOException: Operation not supported)
怎么写一个真正快的通道复制?
别直接调 transferTo 一次——它可能只传一部分(尤其网络文件系统或磁盘满时),必须循环直到全部完成。
同时要确保两个通道都正确关闭,且源通道不能是 InputStream.getChannel() 这种非 FileChannel 实例(否则 transferTo 报 NonReadableChannelException)。
立即学习“Java免费学习笔记(深入)”;
- 用
FileChannel.open(path, READ)和FileChannel.open(path, WRITE, CREATE)显式获取FileChannel - 循环调用
transferTo(position, count, targetChannel),每次更新position,检查返回值是否为0(表示暂无可读数据,需等待或重试) - 加
try-with-resources包住两个通道,避免泄漏;StandardOpenOption.CREATE要配TRUNCATE_EXISTING防覆盖残留
try (FileChannel src = FileChannel.open(srcPath, StandardOpenOption.READ);
FileChannel dst = FileChannel.open(dstPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
long pos = 0;
long size = src.size();
while (pos < size) {
pos += src.transferTo(pos, size - pos, dst);
}
}
Files.copy 的常见误用和修复
最常踩的坑是忽略 StandardCopyOption.REPLACE_EXISTING ——目标文件存在时直接抛 FileAlreadyExistsException,而不是覆盖。
另一个是误传 LinkOption.NOFOLLOW_LINKS 导致符号链接被当普通文件复制(内容变成路径字符串),实际想保留链接就得用 copy 的重载版本并配合 Files.isSymbolicLink 手动处理。
- 覆盖文件必须显式传
StandardCopyOption.REPLACE_EXISTING,没有默认行为 - 想复制符号链接本身(不是它指向的目标),得先判断:
if (Files.isSymbolicLink(src)) { Files.createSymbolicLink(dst, Files.readSymbolicLink(src)); } - 权限复制依赖
PosixFilePermissions和文件系统支持;Windows 下copy会忽略OWNER_WRITE类选项
性能到底差多少?什么时候该换方案?
实测(Linux ext4,SSD,100MB 文件):Files.copy 约 120ms,transferTo 循环约 85ms —— 快 30%,但对应用层感知不强;换成 1GB 文件,差距拉到 300ms vs 180ms。
真正影响决策的不是这点毫秒,而是场景约束:如果你要复制的是 jar:/xxx.txt 这种 JAR 内资源,FileChannel 根本不可用,只能用 Files.copy 配合 InputStream;如果目标是 HDFS 或 S3,那两种都得换 SDK。
- 优先用
Files.copy:逻辑简单、可维护、兼容性好,95% 场景够用 - 只在高频复制 >10MB 本地文件、且明确控制运行环境(Linux + ext4/xfs)时,才值得上
transferTo循环 - 别为了“理论上快”强行统一替换——IO 瓶颈往往不在复制函数本身,而在磁盘队列、缓存命中率或 JVM GC 压力
通道复制的循环逻辑容易写错边界,transferTo 返回值含义和重试条件比表面看起来复杂得多,多数人抄示例时漏了 pos += 或没处理 0 返回,结果静默卡住或复制不全。











