备份前必须检查源路径和权限:先用os.Stat验证源文件存在且可读,再用os.MkdirAll确保目标父目录存在;推荐使用Go 1.20+的os.CopyFile以提升大文件性能,但需额外调用os.Chmod和os.Chtimes保留权限与时间戳。

备份前必须检查源路径和权限
Go 的 os.Open 和 io.Copy 不会自动创建父目录,也不会提示权限不足——它只会返回 "permission denied" 或 "no such file or directory",但错误信息模糊,容易误判为路径写错。
实操建议:
• 先用 os.Stat 检查源文件是否存在且可读:
if _, err := os.Stat(src); os.IsNotExist(err) { /* 处理不存在 */ }• 对目标路径,用
filepath.Dir(dst) 提取父目录,再调用 os.MkdirAll 确保路径存在• 不要跳过
err 检查——哪怕只是 os.CopyFile(Go 1.20+)也得判断返回值
用 os.CopyFile 还是手写 io.Copy?
os.CopyFile(Go 1.20+)底层已优化:在支持的文件系统上会尝试使用 copy_file_range 系统调用,避免用户态内存拷贝,对大文件明显更快;而手写 io.Copy + os.Create + os.Open 需自己处理缓冲区大小(默认 32KB),小缓冲易拖慢,大缓冲占内存。
但注意:
• os.CopyFile 不复制文件权限(mode)和修改时间(mtime),如需保留,得额外调用 os.Chmod 和 os.Chtimes
• 旧版本 Go(• 若需断点续传或校验(如 SHA256),必须绕过 os.CopyFile,改用 io.CopyN 或分块读写
并发备份多个文件时小心竞争和资源耗尽
直接用 go func() {...}() 启动几十个 goroutine 拷贝不同文件,看似快,实际可能触发:
• 文件描述符耗尽(too many open files),因每个 os.Open 占一个 fd
• 磁盘 I/O 饱和,反而比串行还慢
实操建议:
• 用带缓冲的 channel 控制并发数,例如限制最多 4 个并发:
sem := make(chan struct{}, 4)• 每次启动 goroutine 前
sem <- struct{}{},结束后 <-sem• 避免在 goroutine 内部直接 defer
dst.Close() —— 如果 dst 打开失败,defer 会 panic;应先检查 err 再决定是否 defer备份后校验不能只靠 os.SameFile
os.SameFile 只比较 inode 和 dev,适用于硬链接判断,但无法验证内容一致性。备份出错(如磁盘写满中途失败)时,目标文件可能比源文件小,但 os.SameFile 仍返回 false(因不是同一文件),毫无意义。
真正可用的校验方式:
• 小文件(os.ReadFile 读取双方内容,bytes.Equal 对比
• 大文件:计算 SHA256,用 crypto/sha256.New() 分块读取源和目标,避免内存爆炸
• 生产环境建议加时间戳和校验和日志,例如写入 backup_20240615.log 记录每个文件的 size 和 sha256
立即学习“go语言免费学习笔记(深入)”;


















