io.Copy是最稳妥的默认文件拷贝方式,自动处理缓冲、分块读写和错误传播,但不检查目录、不创建父目录、不保留权限与时间戳;Go 1.19+推荐使用os.CopyFile。

用 io.Copy 是最稳妥的默认选择
绝大多数情况下,直接用 io.Copy 拷贝文件就够了——它自动处理缓冲(默认 32KB)、分块读写、错误传播,不关心文件大小,GB 级文件也不会爆内存。
但要注意三点:它只管字节流,不检查源是不是目录;不创建目标路径的父目录;也不保留权限、时间戳等元信息。
- 必须手动调用
os.Open和os.Create,并确保用defer file.Close() - 目标路径的父目录不存在时,
os.Create会直接报no such file or directory,得提前用os.MkdirAll(filepath.Dir(dst), 0755) - 若需校验完整性,可对比
io.Copy返回的字节数与source.Stat().Size()
想保留权限和修改时间?得手动补全 os.Chmod 和 os.Chtimes
io.Copy 复制完只是内容一致,目标文件的权限可能是默认的 0644,时间戳是当前时间。对可执行脚本、配置文件或备份场景,这往往不够。
正确做法是先 source.Stat() 获取源文件信息,再分别设置:
立即学习“go语言免费学习笔记(深入)”;
- 权限:用
os.Chmod(dst, info.Mode() & os.ModePerm)(注意 &os.ModePerm,否则可能误设文件类型位) - 时间戳:用
os.Chtimes(dst, info.ModTime(), info.ModTime()),第二个参数是访问时间,第三个是修改时间 - Windows 下
os.Chmod对部分权限位无效,别指望完全还原 Linux 的0755
大文件卡住?先排查是否漏了 Close 或路径不存在
现象是程序在 io.Copy 调用处长时间无响应,CPU 低、磁盘没动静——这通常不是 io.Copy 本身的问题,而是 I/O 阻塞在上游或下游。
- 最常见原因是目标路径父目录不存在,
os.Create失败但你没检查 error,后续dst是 nil,io.Copy(dst, src)会 panic 或静默卡死 - 另一个高频问题是忘了
defer src.Close(),尤其在循环拷贝多个文件时,句柄耗尽后新os.Open就会阻塞 - 如果目标是网络文件系统(如 NFS),延迟高可能导致单次
Write阻塞,此时可考虑用io.CopyBuffer指定更大缓冲区(如64 * 1024)减少系统调用频次
Go 1.19+ 推荐直接用 os.CopyFile
如果你用的是 Go 1.19 或更高版本,os.CopyFile(src, dst, 0) 是更优解:它内部已封装权限继承、原子性 rename、临时文件中转和错误重试逻辑,比手写 io.Copy 更健壮。
但它不支持跨文件系统硬链接或 reflink,遇到只读文件也更友好;旧版本可通过升级或引入 golang.org/x/tools/go/packages 兼容层替代。
唯一限制是它不提供进度回调或自定义缓冲区,如需监控拷贝进度,还是得回退到 io.Copy + io.TeeReader 组合。
真正容易被忽略的不是怎么写,而是「谁来保证父目录存在」「谁来处理符号链接」「谁来应对磁盘满时的半截文件」——这些边界问题不会在小样例里暴露,但上线后往往第一个出事。










