io.copy仅复制字节流,不处理权限、时间戳、父目录创建及错误分类;需配合os.stat获取元信息、os.mkdirall创建父目录、dst.chmod/chtimes设置属性,并用errors.is区分os.errnotexist等错误。

直接用 io.Copy 就能搞定,但漏掉权限、时间戳、父目录检查或错误判别,拷出来的文件大概率不可用——尤其在备份、部署等场景下,表面成功实则失效。
为什么 io.Copy 拷完文件却没权限、时间不对
io.Copy 只搬运字节,不碰任何元信息。目标文件默认是 0644(Linux/macOS)或只读位(Windows),ModTime 是创建时刻,跟源文件毫无关系。
- 先用
os.Stat(src)获取os.FileInfo,从中取Mode()和ModTime() - 拷完后立刻调
dst.Chmod(info.Mode())—— 注意 Windows 会忽略除只读外的其他位 - 调
os.Chtimes(dst, info.Atim(), info.Mtim());Go 1.19+ 才有Atim(),旧版只能设time.Now()或跳过 - 若源是符号链接,
os.Open会自动解引用;要保留链接本身,得用os.Readlink+os.Symlink单独处理
目标路径不存在或没权限,io.Copy 却不报错
os.Create 遇到父目录不存在时返回 "no such file or directory",但错误信息不指明是哪一级路径缺失;os.Open 权限不足也只报 "permission denied",容易误判为文件名写错。
- 拷贝前主动检查:用
os.Stat(src)确认源存在且可读,用os.IsNotExist分类处理 - 对目标路径,提取父目录:
dir := filepath.Dir(dst),再os.MkdirAll(dir, 0755) - 不要依赖
io.Copy的返回值判断路径问题——它只管读写环节,路径合法性由os.Open和os.Create决定 - Windows 下注意长路径限制,优先用
filepath.Join拼接,别用字符串+
大文件拷贝卡住、内存暴涨或慢得离谱
根本原因不是 io.Copy 本身,而是底层 I/O 阻塞、缓冲区失配,或跨文件系统/挂载点导致 syscall 降级。
- 给
*os.File设置超时:src.SetReadDeadline(time.Now().Add(30 * time.Second)),避免 NFS 或 USB 设备挂起时无限等待 - 默认 32KB 缓冲在 SSD 上偏小,可改用
io.CopyBuffer(dst, src, make([]byte, 1(1MB)提升顺序吞吐 - 避免把
http.Response.Body或未封装的管道直接喂给io.Copy——它们可能无 EOF 或阻塞逻辑异常 - Linux 下
io.Copy会尝试splice(2)零拷贝,但仅限普通文件间复制;遇到 fuse/NFS 自动降级为用户态缓冲,无需干预
要不要用 os.CopyFile(Go 1.20+)
它比手写 io.Copy 快,但只是“内容快”,其余全是裸的——没有父目录创建、不保留权限、不设时间戳,错误提示也更模糊。
- 适合内部脚本中已确保路径存在、且只需内容一致的场景(如临时缓存)
- 若需
cp -p效果,仍得补os.Chmod+os.Chtimes,代码行数差不多 - 它不支持自定义缓冲区,GB 级文件在某些存储上吞吐不如手动
io.CopyBuffer - 旧项目升级前务必确认 Go 版本,
os.CopyFile在 Go 1.19 及更早版本不可用
真正麻烦的从来不是拷数据,而是让目标文件在权限、时间、路径、原子性上和源完全对齐——这些事 io.Copy 不做,也没法靠加个 flag 解决。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











