当前位置:首页 > 文章列表 > Golang > Go教程 > Go bufio.Scanner 如何自定义分隔符读取记录

Go bufio.Scanner 如何自定义分隔符读取记录

来源:17golang原创 2026-09-12 10:59:31 0浏览 收藏

如果输入不是按换行分隔,而是用 ||@@ 这类业务标记分隔记录,Go 仍然可以继续使用 bufio.Scanner。做法是给 Scanner 注册一个自定义 SplitFunc:找到完整分隔符就返回一条 token,暂时找不到就等待更多字节,读到 EOF 再把末尾余量交出来。

要点速览
  • Scanner.Split 接收的是函数,不是单个字符;多字节分隔符可用 bytes.Index 查找。
  • 没有完整记录时必须返回 0, nil, nilatEOF 时要处理没有尾部标记的最后一条记录。
  • 默认 token 缓冲上限是 64 KiB,长记录先用 Buffer 调整,仍需要更强控制时考虑 bufio.Reader

自定义 SplitFunc 要同时处理分隔符和 EOF

Scanner 的默认切分规则是按行读取;Split 可以把它替换为业务规则。一个 SplitFunc 每次面对当前字节片段时,需要返回“应跳过多少字节”“本次 token 是什么”和“是否出错”三个结果。分隔符还没收完整时,不能急着把片段当成记录。

Go bufio.Scanner 自定义 SplitFunc 中 Scanner、输入字节、分隔符和 token 的静态关系图
图1:看清输入字节、分隔符和 EOF 标记如何共同约束 SplitFunc 的 token 返回边界。

下面的函数支持多字节标记,并允许输入末尾没有 ||

package main

import "bytes"

// splitByMark 按完整字节分隔符切分记录;分隔符本身不会进入 token。
func splitByMark(mark []byte) func([]byte, bool) (int, []byte, error) {
    return func(data []byte, atEOF bool) (int, []byte, error) {
        if i := bytes.Index(data, mark); i >= 0 {
            // advance 必须越过整个分隔符,避免下一次扫描重复看到它。
            return i + len(mark), data[:i], nil
        }
        if atEOF && len(data) > 0 {
            // 最后一条记录没有结束标记时,仍然把剩余字节交给调用方。
            return len(data), data, nil
        }
        // 分隔符可能被拆在两次读取之间,先保留当前数据等待补齐。
        return 0, nil, nil
    }
}

这里的 advance 是字节数,不是字符数,所以要写成 i + len(mark)。如果返回 0, nil, nil,Scanner 会继续向底层 Reader 请求数据;若直接返回当前片段,跨读取边界的分隔符就可能被误判。

给 Scanner 配置切分规则,再检查 Err

配置顺序很简单:先创建 Scanner,再设置 Split 和 Buffer,最后进入 Scan 循环。Buffer 必须在扫描开始前调用,max 表示单个 token 允许达到的最大缓冲范围。

package main

import (
    "bufio"
    "fmt"
    "strings"
)

// readRecords 演示按 || 读取订单号,并在循环结束后区分 EOF 与真正错误。
func readRecords(input string) error {
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(splitByMark([]byte("||")))
    // 允许单条记录最多约 1 MiB;初始缓冲只按需分配。
    scanner.Buffer(make([]byte, 1024), 1024*1024)

    for scanner.Scan() {
        // Text 在本轮扫描内转成字符串;需要跨轮保存时应复制数据。
        fmt.Printf("record=%q\n", scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        return fmt.Errorf("scan records: %w", err)
    }
    return nil
}

例如输入 order-1001||order-1002||order-1003,循环会得到三条记录,即使最后没有额外的 ||。Scanner 的正常结束不会把 EOF 当成错误;但底层读取失败或 token 超过上限,会在 Err() 中暴露。

空记录、token 生命周期和缓冲区上限不能混为一谈

连续分隔符会产生空 token。例如 A||||B 中间存在一条空记录。如果业务不允许空记录,要在循环里显式跳过或报错,不要在 SplitFunc 里悄悄改变记录语义。

Scanner.Bytes() 返回当前 token 的字节切片,下一次扫描后不要继续依赖它;需要放入队列、异步处理或长期保存时,复制一份。Scanner.Text() 适合直接得到字符串,但同样不应把“本轮 token”当成永久缓冲。

官方文档给出的默认最大 token 缓冲是 64 KiB,实际还可能需要容纳分隔符。可以用 Buffer 提高上限,但上限越大,异常输入占用的内存边界也越宽:

现象处理方式判断依据
记录短、边界固定直接用 Scanner自定义 SplitFunc 足够表达规则
偶发长记录先调用 Buffer可接受明确的单条记录上限
记录可能极长或需分段恢复改用 bufio.Reader需要更强的错误控制和读取过程控制
Go Scanner Buffer 与 token 长度上限及 bufio.Reader 替代边界关系图
图2:对照 token、Buffer 和 Reader 的边界,判断记录过长时该调整 max 还是更换读取器。

什么时候不该继续扩大 Scanner 的 max

Buffer 解决的是“已知上限内的长 token”,不是无限长度输入的通用方案。如果输入来自不可信网络、单条记录可能持续增长,或者程序需要在超长数据中逐段恢复,继续把 max 调到很大只会把风险推迟。此时用 bufio.Reader.ReadStringReadBytes 或更细粒度的读取逻辑,通常更容易控制内存和错误路径。

落地前可以按这份清单判断:分隔符是否可能跨读取边界;末尾无分隔符是否仍算有效记录;连续分隔符是否允许空记录;记录长度上限是多少;token 是否会交给异步任务;扫描结束后是否检查了 Err()。这些问题都明确后,自定义 Scanner 就会从一个小技巧变成稳定的输入边界。

常见问题

SplitFunc 能按中文字符串分隔吗?

可以。Scanner 面向字节,中文分隔符应使用 UTF-8 编码后的 []byte,例如 []byte("结束");不要按 rune 下标去计算 advance

为什么最后一条记录读不到?

通常是 SplitFunc 只查找分隔符,没有在 atEOF 且仍有数据时返回余量。补上 EOF 分支即可读取没有尾部分隔符的记录。

Scanner 报 token too long 怎么办?

如果记录长度有可靠上限,在首次 Scan 前调用 Buffer 设置更大的 max;如果长度不可控或需要分段恢复,应改用 bufio.Reader

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
LiblibAI SD在线绘图如何把草图生成成品?图生图操作步骤LiblibAI SD在线绘图如何把草图生成成品?图生图操作步骤
上一篇
LiblibAI SD在线绘图如何把草图生成成品?图生图操作步骤
Go JSON 数字进 interface 后为什么变成 float64
下一篇
Go JSON 数字进 interface 后为什么变成 float64
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    98次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    28次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    252次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    180次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    113次使用