os.ReadFile 仅适用于≤5MB小文件,读大文件会因一次性分配全量内存导致OOM或卡死;bufio.Scanner需显式扩容缓冲区以防“token too long”错误。

os.ReadFile 读大文件会卡死或 OOM?别用它
直接调用 os.ReadFile 读取几十 MB 以上的文本文件,大概率触发内存暴涨甚至进程被系统 kill。它内部会一次性分配等同于文件大小的 []byte,且不做任何流控。
常见错误现象:程序卡住数秒、RSS 内存飙升到 GB 级、runtime: out of memory panic。
- 仅适用于小文件(建议 ≤ 5MB),比如配置文件、JSON 模板
- 无法中断或进度反馈,出错时整块丢失
- UTF-8 解码隐含在
string(data)中,非 UTF-8 编码会乱码,且无法定制解码器
bufio.Scanner 按行读取大文本文件,但必须设 Buffer
bufio.Scanner 是处理日志、CSV、纯文本大文件最常用的方式,但它默认单行上限 64KB —— 遇到超长日志行或拼接 JSON 就报 scanner: token too long。
正确做法是显式扩容缓冲区,且需兼顾内存与安全性:
立即学习“go语言免费学习笔记(深入)”;
- 用
scanner.Buffer(make([]byte, 64*1024), 1 设置初始 64KB、上限 1MB - 避免设过大上限(如
1),否则恶意超长行可耗尽内存 - 循环中必须在
for scanner.Scan()结束后检查scanner.Err(),否则 I/O 错误会被忽略 -
scanner.Text()返回新字符串副本,安全;scanner.Bytes()返回底层缓冲切片,若存入 map 或 slice,整块缓冲区无法 GC
写入大文件别用 os.WriteFile,改用 bufio.Writer + Flush
os.WriteFile 对大内容毫无优势:它先把全部数据拷贝进内存切片,再一次性 write(2)。实测写 500MB 文件,比流式写慢 3–5 倍,内存峰值翻倍。
真正高效的做法是控制缓冲节奏:
- 用
f, _ := os.Create("out.txt")打开文件,再包一层w := bufio.NewWriterSize(f, 1(1MB 缓冲) - 每次写入用
w.Write([]byte(line))或w.WriteString(line) - 写完必须调用
w.Flush(),否则数据可能滞留在缓冲区不落盘 - 缓冲区太小(如默认 4KB)会导致 syscall 过多;太大(如 100MB)失去流控意义,且延迟暴露写权限/磁盘满等错误
需要更高性能?优先考虑 io.Copy + bufio.Reader
如果只是复制、转码、过滤行,而非逐行解析逻辑,io.Copy 是最优解 —— 它内部会自动选择零拷贝路径(如 Linux 的 sendfile),减少内存拷贝和系统调用次数。
搭配 bufio.NewReaderSize 可进一步优化吞吐:
- 示例:
io.Copy(dstWriter, bufio.NewReaderSize(srcFile, 1 - 对二进制或固定格式文本(如 Protocol Buffers 日志),比
Scanner更轻量、更稳定 - 不适用于需按字段切分的场景(如固定宽度),此时应换用
bufio.Reader.ReadBytes('\t')手动控制 - 注意:若源 reader 本身已带缓冲(如
gzip.Reader),再套一层bufio.Reader可能冗余


















