当前位置:首页 > 文章列表 > Golang > Go教程 > Go bufio.Scanner 处理空行与扫描错误

Go bufio.Scanner 处理空行与扫描错误

来源:17golang原创 2026-10-31 11:36:04 0浏览 收藏

用 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。空行要不要忽略,是调用方的选择。

Go bufio Scanner 按行返回非空行和空行 token 的关系结构图
图1:ScanLines、Scan 和 Text 的关系说明图;空行也会形成一个长度为零的 token,不是运行截图。

如果空行代表记录之间的分隔符,应该保留它;如果只是日志中的视觉留白,可以在拿到 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(),就会把“读取了一半”误认为“文件完整读完”。

Go bufio Scanner 空行路径与扫描错误路径的边界结构图
图2:空行 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 负责把明确的单行上限传达给运行时。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go bufio.Scanner 提高超长 token 读取上限Go bufio.Scanner 提高超长 token 读取上限
上一篇
Go bufio.Scanner 提高超长 token 读取上限
Go io.Pipe 写端关闭后读端为何收到 EOF
下一篇
Go io.Pipe 写端关闭后读端为何收到 EOF
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    809次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    798次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    890次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    862次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    687次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码