默认 bufio.Scanner 因单行超长(如 base64/JSON)触发 ErrTooLong 而静默失败,因其默认缓冲区仅 64KB 且需显式调用 Buffer() 扩容;安全做法是 scanner.Buffer(make([]byte, 641024), 10241024),并始终检查 scanner.Err();不可控输入应改用 bufio.Reader。

默认 bufio.Scanner 无法安全读取真实生产环境中的大文件——不是因为文件太大,而是因为某一行太长(比如嵌入的 base64、JSON blob 或堆栈 trace),直接触发 bufio.ErrTooLong 导致中断,且错误容易被忽略。
为什么 scanner.Scan() 突然返回 false 却没报错
这不是 I/O 错误,也不是 EOF;scanner.Err() 会返回 bufio.ErrTooLong,但很多人只检查循环退出条件,忘了在循环后显式调用 scanner.Err() 判断。常见现象包括:
- 日志只打印出前几行,后续静默停止
- 最后一行内容重复覆盖前面所有行(因复用缓冲区)
- 程序 panic 报
scanner: token too long,但堆栈不指向你自己的代码
根本原因是:默认缓冲区上限仅 64KB,且 scanner.Buffer() 必须显式调用才生效——不调就永远卡在 64KB。
怎么安全扩大 scanner.Buffer() 上限
必须同时传两个参数:scanner.Buffer(buf, maxLen),其中:
立即学习“go语言免费学习笔记(深入)”;
-
buf是初始切片,长度不能为 0,也不能小于maxLen;写nil或空切片会 fallback 到默认 64KB -
maxLen是单行最大字节数,建议设为业务可接受的硬上限(如 1MB),别碰math.MaxInt32 - 推荐组合:
buf := make([]byte, 64*1024); scanner.Buffer(buf, 1024*1024) - 设太大(如 10MB)会让异常输入直接拖垮内存;设太小仍会失败
注意:扩容只解决“单行超长”,不解决“内存泄漏”——如果你把 scanner.Text() 存进 []string,所有元素最终都指向同一块内存,全是最后一行内容。
什么时候该放弃 bufio.Scanner,改用 bufio.Reader
当你的输入不可控或需求超出“纯文本去换行”时,Scanner 就成了负担。典型场景包括:
- 第三方日志/用户上传文本,无法预估单行长度
- 需要保留原始换行符(
\r\n)、处理二进制分隔符(如\x00) - 要对超长行做截断、告警或丢弃(
io.LimitReader配合ReadString('\n')更可控) - 需精确控制内存峰值,比如流式解析 JSON:用
json.NewDecoder(strings.NewReader(line)),而非json.Unmarshal([]byte(line), &v)
bufio.Reader.ReadString('\n') 没有内置长度限制,返回字符串自带换行符,可用 strings.TrimSuffix(line, "\n") 清理;遇到 EOF 未找到 \n 时,它会返回已读内容 + io.EOF,需先判断 len(line) > 0 再处理。
真正影响性能的从来不是函数选型,而是 IO 模式和引用管理
95% 的大文件处理瓶颈不在解析逻辑,而在系统调用频率和内存分配方式:
- 永远用
os.Open(),禁用ioutil.ReadFile()或os.ReadFile()——它们会把整个文件 load 到内存 - 给
bufio.Reader或Scanner配 1MB 缓冲区:bufio.NewReaderSize(file, 1024*1024) - 每行处理完立刻释放引用:不要长期持有
scanner.Text(),边读边处理,避免存进全局 slice 或 map - 如果真要保留多行,必须深拷贝:
fullLine := append([]byte{}, scanner.Bytes())或string(scanner.Bytes())
最易被忽略的一点:scanner.Text() 返回的是内部缓冲区的引用,下一次 Scan() 就会覆盖——哪怕你只是把它传给一个闭包或塞进 channel,也得先拷贝。


















