io.Copy 复制文件卡住或不完整,因它仅流式拷贝且不处理文件打开/关闭、不保证落盘;须检查 os.Open/os.Create 错误,复制后显式调用 dst.Close(),大文件或 NFS 场景建议用 io.CopyBuffer 自定义缓冲区。

用 io.Copy 复制文件时为什么卡住或不完整?
因为 io.Copy 本身不处理文件打开/关闭,也不保证写入完成——它只负责把源 Reader 的数据流式拷到目标 Writer,中间出错(比如磁盘满、权限不足)会直接返回错误,但不会自动回滚或清理。
- 务必检查
os.Open和os.Create的返回错误,不能只看io.Copy的结果 - 复制完成后必须显式调用
dst.Close(),否则部分数据可能还缓存在内核页中,stat显示大小正确但实际没落盘 - 若源文件很大且目标是网络文件系统(如 NFS),
io.Copy默认 32KB 缓冲可能不够,可改用io.CopyBuffer配合自定义缓冲区(如make([]byte, 1)提升吞吐
移动文件不能只靠 os.Rename?
os.Rename 本质是系统调用 rename(2),只在同文件系统内原子完成;跨分区、跨挂载点时必然失败,错误通常是 invalid cross-device link。
- 先尝试
os.Rename,捕获syscall.EXDEV错误(Go 1.19+ 可用errors.Is(err, syscall.EXDEV)判断) - 失败后退回到「复制 + 删除」流程:用
io.Copy复制,成功后再调用os.Remove源文件 - 注意:删除前确保复制已
Close目标文件,否则 Windows 下会报The process cannot access the file
如何让复制支持进度反馈和中断?
io.Copy 是阻塞同步操作,原生不提供进度回调。要加进度或可取消,得自己封装 io.Reader 或用 context.Context 控制生命周期。
- 用
io.MultiReader+ 自定义Read方法,在每次读块后更新计数器并检查ctx.Done() - 更简单的方式:用
io.LimitReader分段复制,每段后 sleep 或 select ctx,适合大文件分片场景 - 别在
Read实现里做 heavy I/O(如写日志),会拖慢整体速度;进度更新建议用 channel 异步发出去
Windows 下复制符号链接或特殊属性会丢吗?
会。标准 os.Open + io.Copy 只读取文件内容,完全忽略 symlink、ACL、创建时间、扩展属性等元数据。
立即学习“go语言免费学习笔记(深入)”;
- 符号链接需用
os.Readlink读目标路径,再用os.Symlink重建;普通文件复制不触发此逻辑 - 修改时间可用
os.Chtimes在复制后单独设置,但创建时间(BirthTime)在 Windows 上无法通过 Go 标准库设置 - 如果必须保全所有属性,推荐调用系统命令:
exec.Command("cmd", "/c", "copy", "/B")(Windows)或cp -a(Linux/macOS),但失去跨平台性和细粒度控制
io.Copy 的 err 当成唯一判断依据。










