io.copyn 必须恰好拷贝 n 字节,除非源提前 eof 或出错;若数据不足则返回 io.errunexpectedeof,成功时返回 n 和 nil。

io.CopyN 会拷贝多少字节?看第三个参数 n 的值
io.CopyN 不是“最多拷贝 n 字节”,而是“**必须拷贝恰好 n 字节**”,除非源提前 EOF 或发生错误。如果源数据不足 n 字节,它会返回 io.ErrUnexpectedEOF,而不是安静地少拷贝。
这意味着你不能把它当“安全限流”用——它更像一个断言:我预期这里至少有 n 字节可读。
- 成功时返回
n和nil - 源在读完 n 字节前就 EOF → 返回已读字节数(io.ErrUnexpectedEOF
- 读取中途出错(如网络中断)→ 返回已读字节数和具体错误
想限制拷贝上限?别直接用 io.CopyN,改用 io.LimitReader
真正需要“最多拷贝 n 字节”的场景,应该组合 io.LimitReader 和 io.Copy:
limited := io.LimitReader(src, n) nCopied, err := io.Copy(dst, limited)
这样即使 src 只有 3 字节,io.Copy 也会正常结束,nCopied 是 3,err 是 nil。
-
io.LimitReader在内部计数,读满 n 后自动返回io.EOF -
io.Copy遇到io.EOF就停,不报错 - 比手写循环 +
Read更简洁、更符合 Go 的 io 接口哲学
io.CopyN 的典型适用场景
它适合那些你**明确知道源长度、且必须校验完整性**的地方,比如解析固定头结构、读取协议中定义的精确字段长度:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- HTTP/2 帧头是 9 字节,用
io.CopyN(buf[:9], r, 9)确保不漏读 - 从磁盘读取一个已知大小的加密密钥块(如 32 字节),少一字节就该失败
- 配合
bytes.Reader做单元测试,验证逻辑是否严格按字节数处理
注意:io.CopyN 第三个参数是 int64,但实际能传的值受限于内存和系统调用——别传 math.MaxInt64,容易触发 syscall.EINVAL。
常见错误:把 io.CopyN 当成 io.Copy 的带限版
直接替换会出问题:
// ❌ 错误:原意是“最多拷贝 1024 字节”,但这里要求必须读满 io.CopyN(dst, src, 1024) // ✅ 正确:限制读取上限,允许提前结束 limited := io.LimitReader(src, 1024) io.Copy(dst, limited)
另一个坑是忽略返回值:io.CopyN 返回的 int64 是实际拷贝数,它可能小于 n(出错或 EOF),但很多人只检查 err 而没验证字节数是否达标。
边界情况容易被忽略:当 n == 0 时,io.CopyN 立即返回 0 和 nil;当 src 是 nil 或 dst 是 nil,会 panic —— 这些都不如 io.LimitReader + io.Copy 组合鲁棒。










