读大文件禁用os.ReadFile,应按需选择bufio.Scanner(设缓冲上限)、手动file.Read(复用缓冲区)或io.CopyBuffer(调大缓冲),并严格检查错误与关闭资源。

别用 os.ReadFile 读大文件——它会直接 malloc 整个文件进内存,2GB 日志一开就 OOM,不是 bug,是设计如此。
bufio.Scanner 按行读文本大文件,但必须调 scanner.Buffer
默认 scanner.Scan() 会 panic 报 scanner: token too long,因为单行缓冲上限只有 64KB。日志里带 base64、长 traceID 或 JSON Lines 中某条超长,立马翻车。
- 显式设置缓冲:
scanner.Buffer(make([]byte, 64*1024), 10*1024*1024)—— 第一个参数是初始大小,第二个是最大允许长度(这里设 10MB) -
scanner.Text()返回的是拷贝,安全;若需字节级操作(比如跳过 BOM、算 CRC),改用scanner.Bytes()避免 string 转换开销 - 最后一行没换行符?
scanner.Scan()在 EOF 时仍返回该行,不用额外判断,前提是没触发上面的缓冲溢出
手动 file.Read() 分块读二进制或自定义格式
不是纯文本?比如 protobuf 流、加密块、固定帧结构,Scanner 就不合适了,得自己控节奏。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 预分配固定大小缓冲区,复用:
buf := make([]byte, 4*1024*1024)(4MB),别在循环里反复make - 用
n, err := file.Read(buf),不是io.ReadFull—— 后者在最后一块不足时直接报io.ErrUnexpectedEOF,而这是正常结束信号 - 正确判断 EOF:
if n > 0 && err == io.EOF→ 最后一块有效;if n == 0 && err == io.EOF→ 文件为空或已读完 - 写入时务必传
buf[:n],不是整个buf,否则可能把上一轮残留脏数据一起写进去
io.Copy 边读边写,但慢盘/网络要调大缓冲
io.Copy(dst, src) 看似省心,但在 NFS、Ceph、USB 盘或高延迟云盘上,默认 32KB 缓冲会让吞吐暴跌,甚至卡死报 broken pipe。
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖默认缓冲,尤其目标是慢设备时,改用
io.CopyBuffer并传自定义 buffer:buf := make([]byte, 1024*1024)(1MB) - 源和目标都建议用
os.O_CREATE | os.O_WRONLY | os.O_TRUNC打开,避免追加写导致逻辑错乱 - 写入完成后,先
w.Flush()再w.Close(),并检查两者错误 ——Close()可能触发磁盘 flush 失败
最易被忽略的点:所有流式读写中,defer file.Close() 是必须的,但更关键的是,scanner.Err() 必须在循环结束后检查 —— 它可能包含读取中途的 I/O 错误,而 Scan() 自身只返回 bool,不暴露 err。

















