io.copy 是文件流式复制的首选,它内置32 kib缓冲、自动重试部分写入、严格传播错误且仅在真正eof时退出;使用时须确保:os.open后defer src.close()、os.create或os.openfile创建目标、检查io.copy返回error、按需调用dst.sync(),路径含目录时先os.mkdirall,http场景中body.close须在读取元信息和io.copy之后执行。

io.Copy 是文件流式复制的首选,不是备选
直接用 io.Copy 就对了,别自己写 Read+Write 循环。它内部用 32 KiB 缓冲、自动重试部分写入、严格传播错误,且只在真正 EOF 时退出——手动实现极容易漏掉 0 字节读、部分写失败重试、非 EOF 错误静默忽略这三类问题。
基础文件复制必须做这四件事
缺一不可,否则生产环境会出问题:
-
os.Open源文件后,必须defer src.Close() -
os.Create目标文件(会截断),或用os.OpenFile配os.O_CREATE|os.O_WRONLY|os.O_TRUNC显式控制行为 - 调用
io.Copy(dst, src)后,**必须检查 error 是否为 nil**;返回的字节数只是统计值,不能当作传输完成依据 - 若需确保数据落盘(如日志、关键备份),加一句
dst.Sync();注意它会阻塞并增加延迟,按需启用
路径含目录时,os.MkdirAll 是前置硬性步骤
像 "data/logs/app.log" 这种路径,os.Create 会直接报 "no such file or directory" 错误,不会自动建父目录。
正确做法是提前调用:
err := os.MkdirAll(filepath.Dir("data/logs/app.log"), 0755)
if err != nil {
// 处理创建目录失败
}
注意权限 0755 对目录足够,别用 0644(那是文件权限)。
HTTP 响应体写入文件时,Body.Close 不能少也不能早
典型错误是只记得 defer resp.Body.Close(),却忘了:如果 io.Copy 后还要读 resp.Header 或判断 resp.StatusCode,就不能在 Copy 前关 Body;但 Copy 完不关,连接就一直占着,fd 会耗尽。
稳妥写法:
- 先取完 header、status 等元信息
- 再
io.Copy(outFile, resp.Body) - 最后
resp.Body.Close()(可 defer,但要确保 defer 在所有读操作之后)
另外,os.Create 默认覆盖,若需追加(比如日志轮转),得换用 os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)。
真正难的不是调用 io.Copy 这一行,而是它前后那几行资源管理和边界判断——漏掉任意一个,大流量或弱网下都可能表现为静默失败或 fd 泄漏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











