当前位置:首页 > 文章列表 > Golang > Go问答 > Go bufio.Scanner 如何按空行分隔多段文本

Go bufio.Scanner 如何按空行分隔多段文本

来源:17golang原创 2026-10-16 10:56:35 0浏览 收藏

Go 的 bufio.Scanner 默认使用 ScanLines,每次 Scan() 取得一行文本;遇到空行时,Text() 得到空字符串。它不会自动把多行合并成段落,所以“按空行分隔多段文本”要在扫描循环外维护一个当前段缓冲区:普通行追加,空行提交,连续空行忽略,循环结束再提交一次最后一段。

官方文档:https://pkg.go.dev/bufio#Scanner

下面的写法适合配置文件片段、邮件正文、Markdown 简化解析等场景。本文只处理段落边界,不把超长 token 的缓冲修复混进来。

一、先明确 Scanner 返回什么

Scanner 负责从 io.Reader 取数据,分隔规则由 SplitFunc 决定。默认的 ScanLines 去掉行尾换行符,但仍然可以返回空行;因此段落并不是 Scanner 的内置结果,而是由业务代码根据空行重新聚合出来的。

Go Scanner 从 io.Reader 到段落结果的静态结构说明图
图1:Go Scanner 按行读取并聚合段落的结构说明图,重点看输入扫描、空行 token 与当前段缓冲区的边界;这是静态说明图,不是运行截图。

二、用当前段缓冲区聚合普通行

最小实现是保留一个 current 切片。普通行直接追加,提交时用换行重新拼接。这样不会把每次 Scan 的结果误认为一个完整段落,也能保留段内原有的行结构。

package main

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

// readParagraphs 按空行聚合段落,并把底层读取错误返回给调用方。
func readParagraphs(r io.Reader) ([]string, error) {
    scanner := bufio.NewScanner(r)
    // 显式写出默认规则,让段落判断建立在“逐行”语义上。
    scanner.Split(bufio.ScanLines)

    var paragraphs []string
    var current []string

    // flush 只提交非空段,连续空行不会制造空字符串段落。
    flush := func() {
        if len(current) == 0 {
            return
        }
        paragraphs = append(paragraphs, strings.Join(current, "\n"))
        current = current[:0]
    }

    for scanner.Scan() {
        line := scanner.Text()
        // 这里只判断空行,普通行原样保存,不改写正文空格。
        if line == "" {
            flush()
            continue
        }
        current = append(current, line)
    }

    // 最后一段可能没有尾随空行,必须在 Scan 结束后补交。
    flush()
    if err := scanner.Err(); err != nil {
        return nil, fmt.Errorf("扫描文本失败: %w", err)
    }
    return paragraphs, nil
}

这里的关键不是 strings.Join,而是 flush 的调用位置:空行触发一次,EOF 再触发一次。current[:0] 复用切片容量,但不会把已经追加到 paragraphs 的字符串内容清掉。

三、遇到空行时提交段落

空行提交的是“当前已经收集到的段”,不是空行本身。输入中有两个或更多连续空行时,第一次空行会提交当前段,后续空行只会命中空缓冲区并返回,因此结果不会凭空多出空段。

如果业务需要保留段落内部的换行,上面的 strings.Join(current, "\n") 就可以直接使用;如果只关心段落文本,也可以在提交时改用空格拼接,但那会改变原文的换行语义。

四、在 EOF 和 Err 分别收尾

Scan() 返回 false 既可能表示正常 EOF,也可能表示读取错误。先调用一次 flush 可以覆盖“最后一段没有空行结尾”的正常情况;随后检查 scanner.Err(),避免把半截输入误当成完整结果。

普通行、空行、连续分隔、EOF 与 scanner.Err 的段落边界说明图
图2:段落聚合的输入边界说明图,比较严格空行、空白行、连续分隔、EOF 与 scanner.Err;这是结果语义说明图,不是运行截图。

如果读取源在中途返回错误,函数应该保留错误信息并交给上层决定是否丢弃已收集的段落。示例选择返回 nil 和包装后的错误,表达“这次结果不完整”;如果业务允许部分结果,则可以把 paragraphs 与错误同时返回,但要在接口文档中写清楚。

五、按输入规则选择空行判断

严格空行与只包含空格或制表符的行不是同一个规则。示例使用 line == "",适合只有真正空行才分段的格式。如果上游文本把空白行也当作分隔符,可以把条件替换为 strings.TrimSpace(line) == "",但要接受它会把只含空格的正文行当成分隔线。

// isSeparator 根据输入协议决定“空行”的定义。
func isSeparator(line string, blankSpaceSeparates bool) bool {
    if blankSpaceSeparates {
        // 业务允许空格、制表符单独作为段落分隔。
        return strings.TrimSpace(line) == ""
    }
    // 严格模式只接受长度为零的行。
    return line == ""
}

选择规则时不要顺手调用 TrimSpace 改写每一行。更稳妥的做法是只用它参与分隔判断,真正追加到当前段的仍然保留原始 line。

常见误区

  • 把每次 Scan() 当成段落:默认规则返回的是行,不是段。
  • 只在遇到空行时 flush:没有尾随空行的最后一段会丢失。
  • 忽略 scanner.Err():读取失败时可能把不完整文本当成成功结果。
  • 无条件 TrimSpace:会同时改变正文中的前后空格和分隔判断,应该分开处理。
  • 把连续空行追加为结果:flush 前先判断当前缓冲区,才能保持段落结果紧凑。

速查表

输入情况处理动作结果
普通行追加到 current继续收集当前段
严格空行flush current提交已有段落
连续空行空 current 时跳过不产生空段
EOF最后再 flush 一次保留无尾随空行的段落
scanner.Err 非 nil返回错误不把不完整输入当成功

相关问题

Scanner 能否直接按段落返回?可以自定义 SplitFunc,但段落规则和错误边界会一起进入切分函数;只需要按空行聚合时,先用 ScanLines 再在业务层 flush 更容易维护。

空行后还要保留分隔符吗?如果输出只需要段落文本,通常不需要;如果要重建原文,应额外记录分隔符数量,而不是把它们静默丢弃。

文本可能包含超长单行怎么办?那是 Scanner token 上限问题,应单独评估 Scanner.Buffer 或 bufio.Reader,不要和空行聚合规则混在一起。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go time.Time JSON 格式不符合接口约定的适配方式Go time.Time JSON 格式不符合接口约定的适配方式
上一篇
Go time.Time JSON 格式不符合接口约定的适配方式
Go JSON 重复字段处理差异的兼容清单
下一篇
Go JSON 重复字段处理差异的兼容清单
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    490次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    547次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    572次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    526次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    339次使用