Scanner自定义SplitFunc切词
如果输入不是“按行”或“按空白”切分,而是要同时把逗号、分号当作分隔符,可以给 bufio.Scanner 注册一个自定义 SplitFunc。关键不是在函数里循环取词,而是遵守它的返回协议:找到完整 token 时返回前移字节数和 token;数据还不完整时返回 0, nil, nil;到达 EOF 后仍有尾部数据时,把尾部交付出去。
本文用一个“逗号或分号切词”的小例子说明这个协议,同时把长 token、空 token 和错误处理边界讲清楚。
SplitFunc 的返回值到底怎么配合 Scanner
SplitFunc 的签名是 func(data []byte, atEOF bool) (advance int, token []byte, err error)。data 是当前尚未处理的输入片段,atEOF 表示底层 Reader 已经没有更多数据。三个返回值分别决定 Scanner 前移多少字节、交付什么 token,以及是否停止扫描。
advance必须表示已经消费的字节数,不能为负数。token非空时,下一次Scan可以通过Bytes或Text取到它。- 数据不足以组成一个完整 token 时返回
0, nil, nil,让 Scanner 继续读数据。 err非空会终止扫描;特殊的bufio.ErrFinalToken可用于交付最后 token 后停止。

按逗号和分号返回完整 token
实现时可以先找出当前数据片段中的第一个分隔符。找到后,返回“分隔符之后的位置”作为 advance,而 token 只保留分隔符之前的内容。下面的实现还会去掉 token 两侧空白,但不会把空 token 静默变成一个词。
package main
import (
"bufio"
"bytes"
"fmt"
"strings"
)
// splitFields 把逗号和分号视为分隔符,并保留 Scanner 的增量读取语义。
func splitFields(data []byte, atEOF bool) (advance int, token []byte, err error) {
// 只在当前缓冲区里查找分隔符,不假设一次 Read 就能拿到完整词元。
if i := bytes.IndexAny(data, ",;"); i >= 0 {
// 前移包含分隔符,token 不包含分隔符,避免下一次重复消费它。
return i + 1, bytes.TrimSpace(data[:i]), nil
}
if atEOF {
// EOF 时没有新的数据可读,仍需交付最后一个无分隔符结尾的字段。
if len(bytes.TrimSpace(data)) == 0 {
return 0, nil, nil
}
return len(data), bytes.TrimSpace(data), nil
}
// 当前片段还没有完整分隔符,要求 Scanner 读入更多数据。
return 0, nil, nil
}
func main() {
scanner := bufio.NewScanner(strings.NewReader("Go,Scanner;SplitFunc"))
// Split 必须在第一次 Scan 之前注册。
scanner.Split(splitFields)
for scanner.Scan() {
// Text 会生成独立字符串,适合把当前 token 保存到结果中。
fmt.Println(scanner.Text())
}
// 读取结束后检查底层 Reader 或 SplitFunc 返回的错误。
if err := scanner.Err(); err != nil {
panic(err)
}
}
这个函数的核心是“未找到分隔符时不前移”。如果此时直接返回 len(data),Scanner 可能把一个被分块读入的字段提前截断;如果返回正数却不给 token,也会改变下一轮输入的起点。对于空字段,是否保留要由业务决定:当前示例返回空切片,调用方可以在 Scan 循环中显式过滤。
EOF、空 token 与 Scanner 接入
EOF 不是错误,它只是告诉 SplitFunc 当前不会再有更多字节。最容易遗漏的是最后一个字段:输入以普通字符结束时,分隔符搜索会失败,但 atEOF 为真,此时应该把剩余数据作为最后 token 返回。
注册和容量设置也有明确边界:
| 场景 | 返回或配置 | 作用 |
|---|---|---|
| 数据不完整 | 0, nil, nil | 请求 Scanner 继续读 |
| EOF 仍有尾部字段 | len(data), token, nil | 交付最后 token |
| 需要提前终止 | token, bufio.ErrFinalToken | 无错误结束扫描 |
| 字段可能很长 | scanner.Buffer(buf, max) | 提高允许的 token 上限 |

// 在 Scanner 开始读取前同时设置自定义切词和 token 上限。
scanner := bufio.NewScanner(reader)
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
scanner.Split(splitFields)
for scanner.Scan() {
// 如果业务不接受空字段,可在这里统一过滤。
field := strings.TrimSpace(scanner.Text())
if field == "" {
continue
}
consume(field)
}
// Scan 返回 false 后仍要读取 Err,区分 EOF 与实际错误。
if err := scanner.Err(); err != nil {
return err
}
Split 和 Buffer 都应在扫描开始前调用;一旦第一次 Scan 已经启动,再修改配置会触发 panic。另一个边界是 Scanner 适合有限大小的 token:如果字段可能远超设置的上限,或业务需要更细的读取和恢复策略,应改用 bufio.Reader,不要只把上限无限调大。
常见问题
为什么最后一个词经常丢失?
通常是 SplitFunc 只处理了分隔符命中的分支,没有在 atEOF 为真且 data 非空时返回尾部 token。
为什么 Scanner 读到一半就停止?
先检查 scanner.Err()。可能是 SplitFunc 返回了错误,也可能是 token 超过 Scanner 当前允许的最大尺寸;后者应在扫描前用 Buffer 设置合理上限,或换用 bufio.Reader。
什么时候返回 ErrFinalToken?
当业务已经拿到想要的最后一个 token,不希望 Scanner 继续读取时可以使用它。普通的“读完所有字段”场景不需要主动返回这个特殊错误。
总结:自定义 SplitFunc 的稳定写法是先处理已有分隔符,再处理 EOF 尾部,最后用 0, nil, nil 表示“请继续取数据”。只要始终让 advance 与已消费字节一致,并在 Scan 结束后检查 Err,按逗号、分号或业务分隔符切词就能保持可预测的边界。
bufioReader按分隔符读取日志
- 上一篇
- bufioReader按分隔符读取日志
- 下一篇
- Go bufio.NewWriterSize 怎样设置合适的缓冲区容量
-
- Golang · Go教程 | 22分钟前 |
- Go bufio.NewWriterSize 怎样设置合适的缓冲区容量
- 159浏览 收藏
-
- Golang · Go教程 | 33分钟前 | 标准库 · Go教程 · 分隔符 Go bufio.Reader 日志读取 ReadString ReadBytes ReadLine
- bufioReader按分隔符读取日志
- 152浏览 收藏
-
- Golang · Go教程 | 37分钟前 |
- Go mime.AddExtensionType 怎样注册业务文件的媒体类型
- 214浏览 收藏
-
- Golang · Go教程 | 51分钟前 | go · 模板 · Go Parse html/template FuncMap execute
- Go html/template FuncMap 注册自定义函数的解析顺序与调用边界
- 384浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- texttemplate生成配置文件
- 274浏览 收藏
-
- Golang · Go教程 | 1小时前 | 源码分析 · Go教程 · Go 源码分析 AST遍历 go/ast ast.Inspect
- GoastInspect怎样在遍历语法树时提前停止
- 473浏览 收藏
-
- Golang · Go教程 | 1小时前 | 文件处理 · 标准库 · 性能优化 · Go教程 · 文件上传 Go 流式解析 mime/multipart multipart.Reader
- multipartReader流式解析上传内容
- 355浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- netip拆分IP地址与端口
- 478浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1033次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1032次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1175次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1137次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 979次使用
-
- GScript 编写标准库示例详解
- 2022-12-30 369浏览
-
- 关于Golang标准库flag的全面讲解
- 2023-02-25 344浏览
-
- Golang标准库unsafe源码解读
- 2022-12-29 464浏览
-
- 快速掌握Go语言HTTP标准库的实现方法
- 2022-12-30 327浏览
-
- 深入解析golang bufio
- 2023-01-07 467浏览

