Go bufio.Scanner 处理空行与扫描错误
来源:17golang原创
时间:2026-10-31 11:36:04 329浏览 收藏
用 bufio.Scanner 按行读取文本时,空行不是“没有读到数据”:默认的 bufio.ScanLines 会把它作为一个长度为 0 的 token 返回。真正的扫描错误也不会直接从 Scan() 返回错误,而是要在循环结束后调用 Err() 判断。
因此,稳定的写法是先决定空行是否有业务意义,再在 for scanner.Scan() 结束后检查 scanner.Err()。如果输入可能包含很长的一行,还要在第一次调用 Scan() 之前配置 Buffer,否则默认缓冲区可能导致扫描提前停止。
官方文档:https://pkg.go.dev/bufio
先确认空行到底会返回什么
NewScanner 默认使用 ScanLines。它会去掉行尾的换行符,但不会把中间的空行自动删除,所以输入 alpha\n\n beta 时,三次扫描得到的 token 依次是 alpha、空字符串和 beta。空行要不要忽略,是调用方的选择。

如果空行代表记录之间的分隔符,应该保留它;如果只是日志中的视觉留白,可以在拿到 token 后再跳过。不要用“扫描不到文本就结束”的判断替代 Scan(),因为这会把合法空行和真正的输入结束混在一起。
package main
import (
"bufio"
"fmt"
"strings"
)
func main() {
// 中间包含一个空行,用来观察 ScanLines 的默认行为。
input := "alpha\n\n beta\n"
scanner := bufio.NewScanner(strings.NewReader(input))
for scanner.Scan() {
line := scanner.Text()
// 空行仍然是一次成功扫描;是否跳过由业务规则决定。
if line == "" {
fmt.Println("发现空行")
continue
}
fmt.Printf("保留内容:%q\n", line)
}
// Scan 返回 false 后再检查错误,正常 EOF 不会被当成错误。
if err := scanner.Err(); err != nil {
fmt.Printf("扫描失败:%v\n", err)
}
}
这里用 Text() 把最近一次 token 转成字符串,适合需要长期保存或继续处理的文本。若只需要短暂查看字节,可以使用 Bytes(),但它指向的底层数组可能在下一次 Scan() 后被覆盖。
把空行策略写成明确的业务分支
常见的旧代码会直接把每一行追加到切片,结果把配置文件中的空行也当成有效记录;另一种代码使用 strings.TrimSpace 后再判断,这又会把只含空格或制表符的行一并删除。两种行为都可能是对的,关键是不要无意中改变输入语义。
需要“只跳过真正的空行”时,用 line == "";需要“忽略只含空白字符的行”时,才使用 strings.TrimSpace(line) == ""。如果缩进本身有意义,不要在保存前无条件 Trim。
func collectRecords(scanner *bufio.Scanner, ignoreBlankOnly bool) ([]string, error) {
var records []string
for scanner.Scan() {
line := scanner.Text()
// 只在业务明确要求时跳过空白行,避免误删缩进信息。
if ignoreBlankOnly && strings.TrimSpace(line) == "" {
continue
}
records = append(records, line)
}
// 循环结束后统一返回扫描错误,调用方可以决定是否重试或终止。
if err := scanner.Err(); err != nil {
return nil, err
}
return records, nil
}
扫描结束后一定要检查 Err
Scan() 的返回值只有一个布尔值:true 表示拿到了下一个 token,false 表示没有下一个 token。它既可能是正常读到 EOF,也可能是底层 Reader 返回错误,或者 token 超过了当前允许的最大长度。因此只写循环而不检查 Err(),就会把“读取了一半”误认为“文件完整读完”。

func readLines(r io.Reader) ([]string, error) {
scanner := bufio.NewScanner(r)
var lines []string
for scanner.Scan() {
// Text 只取当前 token;需要保留空行时不要过滤空字符串。
lines = append(lines, scanner.Text())
}
// Err 会忽略 io.EOF,但会返回其他读取错误或 token 过长错误。
if err := scanner.Err(); err != nil {
return nil, err
}
return lines, nil
}
如果输入来自文件,文件本身仍然要由调用方负责关闭;Scanner 不会替你关闭传入的 io.Reader。推荐在打开文件成功后立即注册 defer Close(),并把扫描错误与关闭错误按项目规范处理。
长行导致扫描失败时配置 Buffer
Scanner 默认有内部 token 缓冲上限。遇到单行 JSON、堆栈信息或导出字段特别长的输入时,Scan() 可能返回 false,随后 Err() 报告 token 过长。此时应在第一次调用 Scan() 前调用 Buffer。
func scanLongLines(r io.Reader) error {
scanner := bufio.NewScanner(r)
// 初始缓冲区减少小幅扩容;最大值要覆盖业务允许的单行长度。
scanner.Buffer(make([]byte, 64*1024), 2*1024*1024)
for scanner.Scan() {
// 这里只处理当前行,避免把所有内容一次性堆进内存。
line := scanner.Text()
_ = line
}
// Buffer 不能替代错误检查,超出最大值仍会从 Err 暴露出来。
return scanner.Err()
}
Buffer 的第二个参数是允许分配的最大 token 尺寸,不是“文件最大尺寸”。即使把它调大,也不代表适合用 Scanner 处理无界输入;如果必须控制读取边界、恢复读取过程或顺序执行多轮扫描,应评估 bufio.Reader 等更可控的方案。
按这份清单迁移旧的按行读取代码
| 检查项 | 迁移后的明确行为 |
|---|---|
| 空行 | 明确保留,或在 token 已返回后按规则跳过 |
| 只含空格的行 | 只有业务允许时才使用 TrimSpace 判断 |
| 扫描结束 | 循环后调用 Err,不能只看 Scan 返回 false |
| 超长 token | 在第一次 Scan 前设置 Buffer,并限制最大值 |
| 文件资源 | 由打开文件的一方负责 Close,Scanner 不接管生命周期 |
| 末尾无换行 | ScanLines 仍会返回最后一个非空行 |
回归测试至少覆盖:连续两个换行符、只有空白字符的行、最后一行没有换行符、Reader 返回错误,以及超过 Buffer 上限的单行。这样才能确认“空行处理”没有掩盖“输入未完整读取”的问题。
常见问题
空行会让 Scanner 提前结束吗?
不会。默认 ScanLines 会返回空 token,下一次扫描仍会继续读取后面的内容。
为什么 Scan 返回 false 但 Err 是 nil?
通常是正常到达 EOF。Err() 只报告非 EOF 错误,所以仍然应该调用它来完成判断。
调用 Buffer 后还是报 token too long 怎么办?
确认 Buffer 在第一次 Scan() 前调用,并检查第二个参数是否覆盖真实单行长度;如果输入长度没有可靠上限,应重新评估读取器选择。
Scanner 会自动关闭文件吗?
不会。传入的 Reader 生命周期仍由调用方管理,文件读取完成后要按原有责任链关闭。
把空行、EOF、读取错误和超长 token 分成四种状态后,bufio.Scanner 的行为就比较稳定:循环负责消费 token,Err() 负责揭示失败原因,Buffer 负责把明确的单行上限传达给运行时。
-
369 收藏
-
344 收藏
-
278 收藏
-
464 收藏
-
327 收藏
-
Golang · Go教程 | 20分钟前 | 标准库 · JSON · Go教程 · encoding/json · Go encoding/json bytes.Buffer json.Compact JSON压缩214 收藏
-
304 收藏
-
203 收藏
-
Golang · Go教程 | 26分钟前 | 标准库 · JSON · Go教程 · encoding/json · Go encoding/json Prefix JSON格式化 indent json.MarshalIndent204 收藏
-
Golang · Go教程 | 29分钟前 | 标准库 · JSON · go · encoding/json · Go bytes.Buffer Prefix json.Indent JSON格式化 indent487 收藏
-
392 收藏
-
Golang · Go教程 | 57分钟前 | 标准库 · csv · Go教程 · csv Go error FLUSH CRLF encoding/csv Writer.UseCRLF Windows换行103 收藏
-
Golang · Go教程 | 59分钟前 | 标准库 · 错误处理 · IO · Go教程 · Go eof io.Pipe CloseWithError PipeReader PipeWriter.Close321 收藏
-
365 收藏
-
178 收藏
-
362 收藏
-
154 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习