目标文件为空是因为io.Copy不处理文件打开、关闭和创建:源需os.Open,目标需os.Create或os.OpenFile配O_CREATE|O_WRONLY|O_TRUNC,且复制后必须显式dst.Close(),否则缓冲区数据未刷盘;os.ReadFile、bytes.Buffer等非io.Reader/Writer不能直接传入。

io.Copy 为什么复制完目标文件是空的
因为 io.Copy 只负责在两个接口之间搬运字节,不打开、不关闭、不创建文件。目标文件为空,基本就三种情况:os.Open 错误地用于写入(只读 panic)、os.Create 没成功(路径不存在或权限不足)、或者漏掉 dst.Close() 导致缓冲区未刷盘。
必须用 os.Create 或 os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) 打开目标;源文件用 os.Open 即可;复制后立刻调用 dst.Close(),否则小文件极易误判为“复制成功”。
-
os.ReadFile返回[]byte,不是io.Reader,不能直接喂给io.Copy - 别用
bytes.Buffer中转——整文件进内存,大文件直接 OOM - 别手动包装
bufio.Reader/bufio.Writer,缓冲区叠加可能死锁或延迟写入
如何正确同步权限、时间戳等元信息
io.Copy 只复制内容,权限、修改时间、访问时间全丢。生产环境里漏掉这步,备份或部署大概率失效。
实操分三步:先 os.Stat(src) 获取 os.FileInfo;复制完立即对目标文件调 dst.Chmod(info.Mode());再调 os.Chtimes(dst, info.Atim(), info.Mtim())。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- Windows 上
os.Chmod只影响只读位,传0755没用 - Go 1.19+ 才有
Atim(),旧版本建议设time.Now()或跳过访问时间 - 符号链接会被
os.Open自动解引用;要保留链接本身,得单独用os.Readlink+os.Symlink
父目录不存在、跨文件系统、长路径怎么处理
os.Create 报 “no such file or directory” 不告诉你缺的是哪一级目录;os.Open 报 “permission denied” 也不区分是源不可读还是目标不可写。错误信息模糊,排查常卡住。
拷贝前主动检查和补救:
- 用
os.Stat(src)确认源存在且是普通文件(info.Mode().IsRegular()) - 提取目标父目录:
dir := filepath.Dir(dst),再os.MkdirAll(dir, 0755) - Windows 下优先用
filepath.Join拼路径,避免字符串拼接触发长路径限制 - 跨设备(如 SSD → NFS)时,
io.Copy会自然变慢,这是 syscall 特性,不是 bug
要不要用 os.CopyFile(Go 1.20+)?缓冲区怎么调
os.CopyFile 是 Go 1.20 引入的封装,底层可能调用 copy_file_range 等零拷贝系统调用,在纯内容复制场景下更快。但它不创建父目录、不设权限、不改时间戳、错误提示也更模糊——只适合内部脚本中路径已确保存在、且只需内容一致的场景。
默认 io.Copy 用 32KB 缓冲区,够稳;但 SSD 上顺序写吞吐可提升,建议用 io.CopyBuffer 显式控制:
- 缓冲区太小(
- 太大(>1MB)→ 内存浪费,小文件响应延迟上升
- 推荐值:64KB–1MB,例如
buf := make([]byte, 1024*1024),再传给io.CopyBuffer(dst, src, buf) - 若需中断或超时,
io.Copy本身不支持;得给*os.File设SetReadDeadline/SetWriteDeadline,或封装带context.Context的 reader
真正难的不是写对一行 io.Copy,而是把路径检查、父目录创建、权限同步、错误分类、超时控制这些边角都兜住。少一步,上线后就可能在某个 NFS 挂载点或 Windows 长路径下静默失败。

















