scanner.scan() 返回 false 且非 eof 时大概率是 bufio.errtoolong,需显式调用 scanner.err() 判断;扩大缓冲区须同时设置 scanner.buffer() 的底层数组和最大容量;scanner.text() 返回值不可长期持有,需深拷贝;超长行场景应改用 bufio.reader.readstring('\n')。

scanner.Scan() 突然返回 false 且没到文件尾,大概率是单行超长
这不是 I/O 错误,也不是 EOF,而是 bufio.ErrTooLong。默认缓冲区上限 64KB,遇到含 base64、JSON blob、堆栈 trace 或超长 URL 的日志行,scanner.Scan() 直接返回 false,但你不检查 scanner.Err() 就永远不知道发生了什么。
- 必须在循环结束后显式调用
scanner.Err()判断:如果是bufio.ErrTooLong,说明某一行超过了当前缓冲区上限 -
scanner.Text()此时返回空字符串,不能用它做 fallback 或日志输出 - 别靠
len(line) == 0判断空行——超长行触发错误时 line 也是空的,但含义完全不同
扩大 scanner.Buffer() 必须同时设对两个参数
scanner.Buffer() 第一个参数是初始底层数组,第二个是最大容量;只改第二个参数没用,第一个若为 nil 或太小,Scanner 仍会 fallback 到默认 64KB。
- 安全写法:
buf := make([]byte, 1024*1024); scanner.Buffer(buf, 1024*1024)(支持最长 1MB 行) - 禁止写
scanner.Buffer(nil, 1024*1024)——nil触发默认逻辑,上限还是 64KB - 别设成
math.MaxInt32或随意拍脑袋定 10MB:业务能接受的最大单行长度是多少?按这个值设,多留 20% 余量即可 - 超过 4MB 就该重新评估方案:不是 Buffer 不够大,而是输入格式本身已超出“逐行文本”语义
保留多行内容时,scanner.Text() 不能直接 append 到切片
scanner.Text() 返回的是 Scanner 内部缓冲区的引用,每次 Scan() 都会覆盖同一块内存。循环里写 lines = append(lines, scanner.Text()),最后 lines 里所有元素都指向最后一行。
- 要长期保存,必须深拷贝:
lines = append(lines, string(scanner.Bytes()))或lines = append(lines, scanner.Text())(仅当scanner.Text()被立即消费、不跨迭代生命周期) - 如果只需原始字节且避免 UTF-8 解码开销,用
scanner.Bytes(),但同样要append([]byte{}, scanner.Bytes())拷贝 - 用
strings.TrimSpace(scanner.Text())是安全的——它内部已触发字符串拷贝
什么时候该放弃 Scanner,换 bufio.Reader.ReadString('\n')
当你无法控制输入源(如第三方日志、用户上传文本)、需要单行截断告警、或必须保留换行符时,Scanner 的刚性设计反而成了障碍。
-
reader.ReadString('\n')没有内置长度限制,配合io.LimitReader可实现“读到 2MB 还没见 \n 就停”,并自定义处理逻辑 - 它返回的字符串包含
\n,可用strings.TrimSuffix(line, "\n")去掉;兼容\r\n时建议用strings.TrimRight(line, "\r\n") - 末行无换行符时,
io.EOF会和已读内容一起返回,需先判断len(line) > 0再处理 - 性能略优于 Scanner:少了状态机和 token 复制,尤其在纯流式处理场景下更轻量
scanner.Text() 的复用机制、Buffer() 的双参数约束、以及 scanner.Err() 的强制检查义务,三者缺一都会让程序在大文件上跑得越久越不可靠。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











