当前位置:首页 > 文章列表 > Golang > Go教程 > Scanner自定义SplitFunc切词

Scanner自定义SplitFunc切词

来源:17golang原创 2026-11-15 05:53:05 0浏览 收藏

如果输入不是“按行”或“按空白”切分,而是要同时把逗号、分号当作分隔符,可以给 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 后停止。
SplitFunc 输入数据、EOF边界、前移字节数、当前词元和扫描错误的结构关系说明图
图1:SplitFunc 返回协议结构说明图,展示输入数据、边界标记和三个返回值的关系;它是静态说明图,不是运行截图。

按逗号和分号返回完整 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 上限
Split注册点、Scanner、Buffer上限、EOF、最后token和错误停止的边界关系说明图
图2:EOF 与 Scanner 配置边界说明图,展示最后 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,按逗号、分号或业务分隔符切词就能保持可预测的边界。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
bufioReader按分隔符读取日志bufioReader按分隔符读取日志
上一篇
bufioReader按分隔符读取日志
Go bufio.NewWriterSize 怎样设置合适的缓冲区容量
下一篇
Go bufio.NewWriterSize 怎样设置合适的缓冲区容量
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    1033次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1032次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1175次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1137次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    979次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码