io.copy 是边读边写的流式转发器,不缓存全部数据、不自动关闭资源;它通过循环调用 read/write 实现,遇非临时错误即止,返回已复制字节数和首个错误。

io.Copy 不是用来“读完再写”的搬运工,而是边读边写的流式转发器——它不缓存全部数据,也不会自动关闭任何一方。
io.Copy 的基本调用和阻塞行为
它只接受 io.Reader 和 io.Writer 接口,底层通过循环调用 Read 和 Write 实现。只要任一端返回非临时错误(比如 io.EOF 或网络断连),就立刻停止并返回该错误。
- 常见误用:把
os.Stdin传给io.Copy后程序卡住——因为标准输入默认不会自动 EOF,需手动 Ctrl+D(Unix)或 Ctrl+Z(Windows)触发 - 超时控制必须靠外层:比如用
context.WithTimeout包裹 reader/writer,或换用io.CopyN限定字节数 - 返回值是实际复制的字节数和第一个遇到的错误,
err == nil并不表示“全部复制完成”,而只是“没遇到错误地结束了”
为什么 io.Copy 不关文件或连接
它只负责数据搬运,不持有资源生命周期的控制权。关闭操作必须由调用方显式管理,否则极易泄漏 fd 或连接。
- 典型泄漏场景:
src, _ := os.Open("a.txt"); dst, _ := os.Create("b.txt"); io.Copy(dst, src)—— 两个文件都没关 - 正确做法:用
defer配合Close,且注意关闭顺序(一般先 close writer 再 close reader,尤其涉及网络时) - 如果 writer 是
http.ResponseWriter这类不能随便关的对象,就绝对不能 defer 关它;reader 是http.Request.Body则必须关,否则连接无法复用
替代方案:什么时候不该用 io.Copy
当需要精确控制缓冲、截断、校验或非阻塞行为时,io.Copy 就太“粗放”了。
- 要限制大小:用
io.CopyN(dst, src, 1024*1024),超过直接截断并返回io.EOF - 要带缓冲:自己 new
bufio.Reader/Writer,再传给io.Copy,否则默认用 32KB 内部 buffer,对小量数据不友好 - 要实时处理每块数据:别用
io.Copy,改用io.ReadFull+ 循环Write,中间插校验或转换逻辑 - 要非阻塞:底层 reader/writer 必须支持非阻塞 I/O(如带
SetReadDeadline的net.Conn),io.Copy本身不提供 async 接口
真正容易被忽略的是错误传播路径——比如从 gzip.Reader 复制到 os.File,出错时你拿到的是底层 read 还是 write 错?得看哪个先失败;而 io.Copy 不会帮你 unwrap,得靠类型断言或日志上下文定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











