Go bufio.Scanner 读取超长行怎么安全扩容
用 bufio.Scanner 按行读日志时,短行一切正常,换成一条几十万字节的 JSON 日志却可能直接停止,并在 Scanner.Err() 中看到 bufio.Scanner: token too long。安全的处理方式不是把限制改成一个特别大的数字,而是在第一次 Scan() 前用 Buffer 设定“初始容量 + 最大 token 大小”,再根据业务允许的单行字节数决定是否继续使用 Scanner。
- Scanner 默认最大 token 约为 64 KiB,超长时 Scan 返回 false,必须检查 Err。
- Buffer 只能在第一次 Scan 前调用,max 要覆盖内容和换行符,并保留业务上限。
- 行长不可控、需要分段或要从超长行中恢复时,优先比较 bufio.Reader。
先把 Scanner 的上限和内存边界分开看
NewScanner 默认使用 ScanLines,它把一行作为一个 token 返回。默认缓冲上限是 64 KiB,而且真实可容纳的 token 可能略小,因为输入还需要放下换行符。超出边界后,扫描会不可恢复地停止;这不是“少读了一段”,而是当前 Scanner 已经不能继续完成这次按行扫描。
可以把边界理解成一条静态关系:输入流交给 Scanner,Scanner 通过 ScanLines 找行,token 先放进初始缓冲区;如果不够,缓冲区在最大 token 上限内扩容,超过上限才会落到 ErrTooLong。下面的图只展示这些组件之间的关系,便于先确定应该调哪一个参数。

用 Buffer 在第一次 Scan 前设置可控上限
最小改动是在创建 Scanner 后立即调用 Buffer。初始容量决定一开始准备多少空间,max 决定扫描期间允许申请到多大;二者不是同一个概念。示例把业务单行限制设为 2 MiB,并为 LF 或 CRLF 留出少量余量:
package main
import (
"bufio"
"fmt"
"io"
)
func readLines(r io.Reader) error {
scanner := bufio.NewScanner(r)
// 初始空间保持适中,最大值按业务允许的单行字节数设置。
const maxLineBytes = 2 * 1024 * 1024
// ScanLines 还需要容纳行尾分隔符,留出两个字节余量。
scanner.Buffer(make([]byte, 64*1024), maxLineBytes+2)
for scanner.Scan() {
line := scanner.Text()
// 这里处理一整行;不要把超大行无条件长期放入内存缓存。
_ = line
}
// Scan 返回 false 既可能是 EOF,也可能是 token 太长或底层读错误。
if err := scanner.Err(); err != nil {
return fmt.Errorf("读取文本行失败: %w", err)
}
return nil
}
顺序很重要:Buffer 在扫描开始后调用会 panic。循环结束也不能只看 Scan() 的 false,必须读取 Err();这样 EOF 会被正常区分,超长 token 和底层 I/O 错误也不会被悄悄吞掉。
超长行不是越大越好:按字节预算控制内存
max 应该来自业务边界,而不是拍一个很大的数。日志行、单条 JSON 消息和导入记录可以分别设定上限;超过上限时返回明确错误,让调用方丢弃、转存或改走专门的大记录通道。中文字符数和字节数也不能混用,UTF-8 文本的内存预算应按字节估算。
| 场景 | 建议 | 原因 |
|---|---|---|
| 稳定的小型日志 | 保留默认 Scanner | 边界清晰,代码简单 |
| 已知最大行长 | Buffer(适中初始值, 上限+分隔符余量) | 突破默认值且限制内存 |
| 长度不可控或需恢复 | 比较 bufio.Reader | 可以分段读取并自行处理边界 |
还有一个容易忽略的边界:Scanner.Bytes() 返回的底层数组可能在下一次 Scan 时被覆盖;如果要把当前行交给异步任务或跨循环保存,应复制它,或者直接使用 Text() 接受字符串分配。
什么时候应该换用 bufio.Reader
Scanner 适合“每条记录有明确上限、超限就失败”的输入。若一行可能非常长,或者需要读到分隔符后再决定如何处理,bufio.Reader 更容易控制:可以使用 ReadString 或 ReadBytes,也可以用 ReadLine 分段接收并通过前缀信息拼接。代价是调用方要自己处理片段、EOF 和单行累计大小。
选择关系可以概括为:Scanner 连接 Scanner.Buffer 与固定 token 上限,Reader 则把读取拆成 ReadString、ReadLine 等更细的读取接口;ErrTooLong 是 Scanner 的失败边界,不应把它当成可从中间恢复的信号。

常见问题
Buffer 的 max 是字符数还是字节数?
它对应字节缓冲区大小,应按 UTF-8 输入的字节预算估算,不要直接把“字符数”当成 max。
把 max 设置成 int 的最大值可以吗?
不建议。这样失去输入保护,异常大行可能带来内存压力;应根据日志、消息或记录的业务上限设置。
Scanner 扫描结束后为什么一定要检查 Err?
因为 Scan 返回 false 既可能代表正常 EOF,也可能代表 token 太长或底层读取错误;Err 才能区分这些情况。
Redis Stream 消费组如何处理 Pending 列表里的超时消息
- 上一篇
- Redis Stream 消费组如何处理 Pending 列表里的超时消息
- 下一篇
- VS Code 怎么用 Profiles 隔离不同项目的扩展配置
-
- Golang · Go教程 | 25分钟前 | 性能优化 · Go教程 · runtime Go 内存分配 性能排查 GOEXPERIMENT nosizespecializedmalloc
- Go runtime 怎样理解小对象专用分配器的开关
- 176浏览 收藏
-
- Golang · Go教程 | 25分钟前 |
- Go runtime 怎样用 GODEBUG 关闭 trace 标签
- 294浏览 收藏
-
- Golang · Go教程 | 26分钟前 |
- Go runtime/trace 怎样控制 trace 服务只监听本机
- 216浏览 收藏
-
- Golang · Go教程 | 32分钟前 | goroutine · HTTP服务 · pprof · Go教程 · Go goroutine泄漏 Go诊断 net/http/pprof goroutine profile pprof.Handler
- Go net/http/pprof 怎样暴露 goroutineleak 诊断入口
- 318浏览 收藏
-
- Golang · Go教程 | 33分钟前 |
- Go runtime/pprof 怎样读取 goroutineleak profile
- 264浏览 收藏
-
- Golang · Go教程 | 35分钟前 |
- Go go vet 怎样发现指针错误类型包装
- 239浏览 收藏
-
- Golang · Go教程 | 39分钟前 |
- Go go tool compile 怎样使用 response file 传递编译参数
- 336浏览 收藏
-
- Golang · Go教程 | 41分钟前 |
- Go go fix 怎样用 atomictypes modernizer 迁移原子类型
- 231浏览 收藏
-
- Golang · Go教程 | 41分钟前 | 依赖管理 · go · require go.mod go mod tidy
- Go go mod tidy 怎样合并重复的 require 块
- 254浏览 收藏
-
- Golang · Go教程 | 42分钟前 | 切片 · 标准库 · 工具链 · Go教程 · Go 代码迁移 go fix slicesbackward slices.Backward 反向遍历
- Go go fix 怎样用 slicesbackward modernizer 替换反向遍历
- 484浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 645次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 675次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 718次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 680次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 512次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- go语言中的defer关键字
- 2023-02-17 150浏览
-
- Golang中Interface接口的三个特性
- 2023-01-07 394浏览
-
- go语言中函数与方法介绍
- 2023-01-07 297浏览
-
- go语言数据类型之字符串string
- 2022-12-30 321浏览

