bufio.Scanner 默认单行限64KB,超长需调大Buffer;ReadLine需手动处理isPrefix拼接并拷贝数据;大文件禁用ReadFile+Split,避免OOM;Scanner自动处理各类换行符。

用 bufio.Scanner 读文件最常用,但别忽略它默认 64KB 行长限制
绝大多数场景下,bufio.Scanner 是最顺手的选择,它自动处理换行、隐藏底层 Read 调用,写起来干净。但它默认只接受最长 64KB 的单行——遇到日志里带超长 JSON、或拼接的 base64 字段时,直接报错 scanner: token too long。
解决方法很简单:调大缓冲区
scanner := bufio.NewScanner(file) buf := make([]byte, 64*1024) // 改成 128KB? scanner.Buffer(buf, 128*1024)
- 第二个参数是最大令牌长度(即单行最大字节数),必须 ≥ 缓冲区大小
- 设太大(比如 10MB)会吃内存,尤其文件行数多时;设太小会 panic,不是返回 false
- 如果不确定行长,又不想手动管理 buffer,可切到
bufio.Reader.ReadLine
bufio.Reader.ReadLine 更底层,要自己处理换行和拼接
当行可能被截断(比如网络流、或真有几 MB 一行),ReadLine 是更可控的选择。但它不自动合并多段,每次调用只读一“段”,靠返回的 isPrefix 判断是否没读完。
典型漏掉的点:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 忽略
isPrefix == true时的循环读取,导致数据截断 - 忘记检查
err == io.EOF以外的错误(比如编码非法字节) - 把
ReadLine返回的切片直接存进 map/slice——底层底层数组可能被下一次读覆盖
安全做法是显式拷贝:
for {
line, isPrefix, err := reader.ReadLine()
if err != nil {
if err != io.EOF { log.Fatal(err) }
break
}
if isPrefix {
// 继续读,拼到 line 上
continue
}
fullLine := append([]byte{}, line...) // 拷贝一份
}
大文件别用 ioutil.ReadFile + strings.Split
有人图省事,先把整个文件读进内存再切分:lines := strings.Split(string(data), "\n")。这在几十 MB 以下的小配置文件里没问题,但一旦文件超 200MB,内存占用立刻翻倍(原始字节 + 字符串头 + 每行 slice 头),GC 压力陡增,还容易 OOM。
-
ReadFile本身已弃用,应改用os.ReadFile - 即使加了
runtime.GC()也救不回峰值内存 - 真正需要随机访问所有行时,才考虑 mmap 或分块预加载,而不是全读
Windows 和 Unix 换行符差异会影响 Scanner.Text() 吗?
不影响。bufio.Scanner 默认用 ScanLines 拆分,它对 \r\n、\n、\r 都识别为行结束,并自动去掉这些字符。你拿到的 Text() 结果里不会有换行符残留。
但要注意两个边界情况:
- 文件末尾没换行符时,最后一行仍能正常读出(Scanner 不要求末尾有 \n)
- 如果文件混用
\r\n和\r(极少见),Text()返回的内容末尾可能出现孤立的\r——这不是 Scanner 的 bug,是原始数据问题 - 若需严格清洗,建议在业务逻辑里统一
strings.TrimRight(line, "\r\n")

















