os.ReadFile不能用于大文件,因其内部调用io.ReadAll强制一次性分配整块内存容纳全部内容,1.2GB文件在低内存环境直接触发runtime: out of memory panic;正确做法是用os.Open配合io.Copy或io.CopyBuffer流式分块处理。

os.ReadFile 为什么不能用于大文件
它会直接 panic:runtime: out of memory,不是因为 GC 慢,而是 os.ReadFile 内部调用 io.ReadAll,强制一次性分配整块内存容纳全部内容。哪怕只是读一个 1.2GB 的日志,在容器或低内存环境里,Go 运行时就直接崩溃。
io.Copy 是最简可靠的流式读写入口
别自己写 for 循环读 buf,io.Copy 内部已用 32KB 缓冲区自动分块搬运,语义清晰、错误传播完整、内存恒定。关键点:
- 打开源文件用
os.Open,目标文件建议用os.O_CREATE | os.O_WRONLY | os.O_TRUNC(单 goroutine 场景) - 必须显式
defer src.Close()和defer dst.Close(),否则 fd 泄漏 -
io.Copy返回的 error 可能是写入中途发生的(如write: broken pipe),不是 EOF,要检查 - 若目标是 HTTP 响应体或 NFS 等慢盘,
io.Copy吞吐会掉,此时换io.CopyBuffer(dst, src, buf),buf 推荐64 * 1024到1024 * 1024
需要分块控制或跳过前 N 字节时,用 io.LimitReader + io.CopyN
手动 seek 不通用——http.Response.Body 或管道类 io.Reader 根本不支持 Seek。正确姿势:
- 跳过开头 N 字节:
io.CopyN(io.Discard, r, n),比r.Seek(n, io.SeekStart)更健壮 - 切出固定大小块:
io.LimitReader(r, chunkSize),它返回新 reader,不移动原r的 offset,适合链式处理 - 块大小设为
32 * 1024(32KB)到1024 * 1024(1MB)之间:太小 syscall 过多;太大削弱流控,且在 USB/NFS 上反而卡住 - 写入端若需并发写不同偏移,必须用
os.O_CREATE | os.O_WRONLY+WriteAt,而非os.O_APPEND(它每次写前都 seek 到 EOF,破坏 chunk 可控性)
bufio.Reader 在纯分块场景下基本没用
它的默认 4KB 缓冲只是预读,不改变整体内存模型,也不帮你对齐 chunk 边界。反而容易埋坑:
立即学习“go语言免费学习笔记(深入)”;
- 误用
Peek/UnreadByte会导致跨 chunk 数据错位,尤其在加密、协议头校验等场景 - 若后续逻辑不需要按行(
ReadString)、按分隔符(ReadBytes)或字段解析(CSV),直接用*os.File或bytes.Reader配合io.LimitReader更透明、更易调试 - 只有当你需要
Scanner的行边界语义、或 CSV 解析器依赖缓冲预读时,才值得加一层
真正难的不是“怎么读”,而是块边界与业务逻辑的对齐——比如断点续传要记 offset,加密流要对齐 cipher block size,这些地方缓冲区再大也救不了 offset 管理漏洞。


















