性能差主因是每行一次系统调用;bufio.scanner需调buffer防panic;超长行应换readline或readbytes;writer须显式flush并检查错误;并发写需独立实例;seek后必须reset。

直接用 os.File.Read 读千万行日志,性能差的主因不是 Go 慢,而是每行一次系统调用——1000 万次内核切换拖垮整个流程;用 bufio 不是“加个壳”,是把零散读请求攒成批量,这是最简单、见效最快的优化。
bufio.Scanner 读超长日志行直接 panic?调 Buffer 就对了
默认 bufio.Scanner 缓冲区上限 64KB,遇到带完整堆栈或嵌套 JSON 的错误日志行,scanner.Scan() 立刻返回 false,且不报错——你以为读完了,其实是被 scanner.Err() 卡在 ErrTooLong 上静默终止。
- 必须在
for scanner.Scan() { ... }循环结束后,立刻检查if err := scanner.Err(); err != nil - 扩容缓冲:调
scanner.Buffer(make([]byte, 4096), 1024*1024)设最大容量为 1MB(别设math.MaxInt32,缺换行符时会 OOM) - 若日志中持续出现 >1MB 的单行(如 base64 blob),
Scanner不再适用,该切到bufio.Reader.ReadLine()自己拼接
bufio.Reader.ReadString('\n') 为什么比 Scanner 更可控
当你需要精确控制换行边界、处理 HTTP 响应头、或日志本身含物理换行符(比如 log.Printf("%+v", err) 输出的多行 struct),Scanner 的 ScanLines 分隔逻辑会破坏语义完整性。
-
reader.ReadString('\n')返回string,但遇到超长行仍可能内存暴涨;更稳妥用reader.ReadBytes('\n'),它返回[]byte且不触发 UTF-8 解码 - 注意
ReadBytes返回的切片包含\n,需手动bytes.TrimRight(line, "\n\r") - 初始化时指定缓冲大小:
bufio.NewReaderSize(file, 16*1024),比默认 4KB 更适配平均行长 5–10KB 的 JSON 日志
写入端卡住、丢数据?Flush 没调是头号原因
bufio.NewWriter 的缓冲区只在三种情况下真正落盘:缓冲区满、显式调 w.Flush()、或 w.Close()。忘记 Flush 是“文件空”“程序退出后内容消失”的根源。
- 别只靠
defer w.Close()——Close()虽会调Flush(),但若磁盘满导致Flush()失败,Close()也失败,而你没检查返回值就以为成功了 - 更稳妥收尾:
defer func() { if err := w.Flush(); err != nil { /* 处理 err */ } }() - 并发写同一文件时,每个 goroutine 必须用独立的
*bufio.Writer实例,不能共享
最常被忽略的点:所有 bufio 实例都依赖底层 *os.File 的 offset,file.Seek() 后必须调 reader.Reset(file) 刷新缓冲区,否则下次读先吐缓存旧数据,再从新 offset 继续——逻辑彻底错乱。











