应避免用 bufio.scanner 读不可信大文本,因其默认单行上限64kb,超长行会直接 panic;需显式调用 scanner.buffer() 设置足够缓冲区,或改用 bufio.reader.readstring() 等更可控方式。

别用 bufio.Scanner 读不可信大文本,除非你已显式调大缓冲区并确认行边界可控——否则它会在某一行超长时直接 panic,不是报错,是崩溃。
scanner.Scan() 为什么会 panic: runtime error: makeslice: len out of range
因为 bufio.Scanner 默认单行最大长度是 64 * 1024 字节(64KB)。遇到带 base64、嵌套 JSON、或无换行的二进制 dump 的日志行,它尝试分配超出容量的切片,触发底层 runtime.makeslice 失败。这不是 bug,是设计上对“行”语义的强制保护。
- 错误信息固定为
scanner: token too long,但实际 panic 不会给你捕获机会 -
scanner.Err()在 panic 后根本不会被调用到,所以加if err := scanner.Err(); err != nil也救不回来 - 即使你只处理标准日志,第三方服务写入的字段也可能突然变长(比如 trace ID 被替换成长 UUID)
必须调用 scanner.Buffer() 才算真正启用 Scanner
没调 scanner.Buffer() 就用 scanner.Scan(),等于裸奔。它默认初始 buffer 是 4096 字节,上限 65536 字节,远低于现实场景需求。
- 推荐写法:
scanner.Buffer(make([]byte, 64*1024), 1024*1024)—— 初始 64KB,上限 1MB - 第二个参数不能设成
math.MaxInt32:万一文件末尾缺换行符,它会一路追着读,内存暴涨直至 OOM - 若需长期保存某行内容(比如存进 map),必须拷贝:
string(scanner.Bytes())或append([]byte{}, scanner.Bytes()...),否则下一轮Scan()就覆盖了
scanner.Text() vs reader.ReadString('\n'):选谁更稳
当你需要精确控制换行符行为、兼容 \r\n 混用、或想避免 UTF-8 解码开销时,bufio.Reader.ReadString('\n') 更底层、更可控。
-
scanner.Text()自动去掉\n或\r\n,返回string;reader.ReadString('\n')返回含换行符的[]byte,零拷贝但需手动bytes.TrimRight(line, "\r\n") -
ReadString遇超长行不会 panic,而是返回io.ErrBufferFull,你能主动处理截断或跳过 - 若用
ReadString,务必初始化足够大的 buffer:bufio.NewReaderSize(file, 16*1024),否则频繁重填缓冲区反而拖慢
真正高效读 GB 级文本的关键不在“怎么读一行”,而在 IO 模式
95% 的日志分析不需要“按行”,只需要“流式顺序扫描”。这时候重点是减少系统调用次数和内存分配,而不是纠结于 Scanner 还是 Reader。
- 用
os.Open()+bufio.NewReaderSize(file, 1024*1024)(1MB buffer)比默认 4KB 快得多,尤其在 NVMe 盘上 - 如果只是简单分隔(如按
\n切),reader.ReadString('\n')比scanner.Scan()少一层状态机,实测快 10%~15% - 若后续处理耗时(如正则匹配、网络请求),把读取和处理拆成两个 goroutine,用带缓冲 channel 传递
[]byte,但缓冲区大小要压测——太大会卡住 reader,太小会阻塞 producer
最常被忽略的一点:无论用哪种方式,都必须在循环结束后检查 scanner.Err() 或 reader.Read 的最终错误。I/O 错误往往发生在最后一行之后,不检查就等于假装它不存在。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











