io.Copy 比 os.ReadFile 更适合大文件,因其用固定32KB缓冲区流式处理,内存恒定;而 os.ReadFile 会一次性加载全文件到内存,易导致 OOM。

为什么 io.Copy 比 os.ReadFile 更适合大文件
因为 os.ReadFile 会一次性把整个文件加载进内存,1GB 文件就占 1GB 内存;而 io.Copy 默认用 32KB 缓冲区边读边写,内存占用恒定。实际项目中遇到 500MB 日志归档或视频转存时,直接 panic:out of memory 就是这么来的。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 始终优先用
io.Copy处理 >10MB 的文件 - 若需自定义缓冲区大小(比如 SSD 上调高吞吐),传入带
io.CopyBuffer的版本 - 注意:源
io.Reader和目标io.Writer都必须支持流式操作,不能是已关闭的*os.File
如何安全地边读边处理(如 JSON 行解析)
常见错误是用 bufio.Scanner 读取超长行导致内存暴涨,或未检查 Err() 导致静默截断。正确做法是控制单次读取上限,并显式判断错误类型。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
scanner := bufio.NewScanner(file)后立刻调scanner.Buffer(make([]byte, 4096), 1 限制最大行长度 - 每次
scanner.Scan()后必须检查scanner.Err(),尤其要区分io.EOF和bufio.ErrTooLong - 若处理的是结构化流(如 NDJSON),改用
json.Decoder的Decode()方法,它天然支持流式反序列化
os.OpenFile 的 flag 组合怎么选才不丢数据
误用 os.O_CREATE | os.O_WRONLY 而不加 os.O_TRUNC 会导致写入位置从文件开头开始覆盖,但旧数据尾部残留;加了 os.O_APPEND 又可能破坏原子性。日志轮转、断点续传这类场景极易出错。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 追加写入(如日志):只用
os.O_CREATE | os.O_WRONLY | os.O_APPEND,系统保证写入位置在末尾 - 覆盖写入(如配置更新):必须显式加
os.O_TRUNC,且建议先写临时文件再os.Rename - 断点续传:打开时用
os.O_CREATE | os.O_RDWR,再用file.Seek(0, io.SeekEnd)定位,避免依赖os.O_APPEND的竞态
流式压缩/解压时为何 gzip.NewReader 报 “invalid header”
典型原因是底层 reader 已被提前消费(比如用 io.Copy 读过前几个字节做 magic check),导致 gzip.NewReader 拿不到完整的 gzip header。这个问题在 HTTP body 解包、分片上传合并时高频出现。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 永远不要对同一个
io.Reader多次构造解压器;需要预检时,用io.MultiReader或bytes.NewReader复制 header 字节 - 更稳妥的做法:用
gzip.NewReader(io.TeeReader(src, hashWriter)),把校验和计算和解压串在一起 - 如果源是
*os.File,优先用file.Seek(0, io.SeekStart)重置偏移量,而不是反复创建新 reader
流式处理真正的难点不在 API 调用,而在边界条件——谁关文件、谁清缓冲、错误后偏移是否可恢复。这些细节不会报编译错误,但会让服务在线上跑三天后突然卡死。










