当前位置:首页 > 文章列表 > Golang > Go教程 > Go ascii85.NewEncoder Flush 后为什么还要 Close

Go ascii85.NewEncoder Flush 后为什么还要 Close

来源:17golang原创 2026-11-10 19:13:19 0浏览 收藏

在 Go 里把一段二进制数据流写成 ASCII85 文本时,ascii85.NewEncoder 和外层 bufio.Writer 可能各自留着一部分数据。只调用外层的 Flush 并不能替编码器完成最后一块;正确收尾是先关闭 ASCII85 编码器,再刷新外层写缓冲。原因很具体:ASCII85 按 4 个输入字节组成一个 32 位块,尾部不足 4 字节时要等 Close 才会编码写出。

官方文档:https://pkg.go.dev/encoding/ascii85#NewEncoder

要点速览
  • NewEncoder 返回的是 io.WriteCloser,不是只需要 Write 的普通 Writer。
  • Close 负责清空 ASCII85 编码器自己的尾部缓冲,外层 Flush 只负责把已经写入外层 Writer 的内容推出去。
  • 嵌套写入器的收尾顺序是 encoder.Close(),然后 buffer.Flush(),两个错误都要处理。

先看清楚两层缓冲各自保存什么

这次问题通常出现在“写入没有报错,但结果末尾缺几个字符”的场景。NewEncoder(w) 会把输入交给 ASCII85 编码器;编码器内部用 4 字节缓冲等待一个完整块,已经完成的块才会写入 w。如果 w 又是 bufio.Writer,那么编码结果还会在外层继续暂存。

因此,bufio.Writer.Flush() 看到的只是“已经从 ASCII85 编码器写进来的内容”。它不知道编码器内部是否还握着 1~3 个原始字节,也不会主动调用编码器的 Close。

ASCII85 编码器的四字节尾部缓冲、外层 bufio.Writer 与最终 Writer 的静态关系说明图
图1:静态结构说明图,查看 ASCII85 编码器与外层写缓冲的两层边界;这不是运行截图。

旧写法的风险:Flush 只刷新了外层

下面的写法把两个动作混在一起了:

var dst bytes.Buffer
buf := bufio.NewWriter(&dst)
enc := ascii85.NewEncoder(buf)

// 写入 3 个字节时,编码器仍可能把它们留在自己的尾部缓冲中。
_, err := enc.Write([]byte("abc"))
if err != nil {
    return err
}

// 这里只能刷新已经到达 buf 的编码结果,不能替 enc 补齐尾块。
return buf.Flush()

如果输入长度刚好是 4 的倍数,这个问题可能暂时不显眼,因为完整块已经编码;一旦最后一段只有 1~3 个字节,就会出现截断。这个偶尔“看起来正常”的特征,正是它容易在回归测试中漏掉的原因。

正确写法:先 Close,再 Flush

Close 的语义不是关闭底层文件或网络连接,而是结束这个 ASCII85 编码器并写出待处理的尾部。标准库实现会在 Close 中检查内部缓冲;如果还有剩余字节,就用短块规则编码后写到传入的 Writer。之后才轮到外层 bufio.Writer.Flush 把这些新写入的字符送到最终 Writer。

func writeASCII85(dst io.Writer, src []byte) error {
    // 外层缓冲只负责暂存已经编码好的文本。
    buf := bufio.NewWriter(dst)
    enc := ascii85.NewEncoder(buf)

    // Write 可能只把最后 1~3 个原始字节留在 enc 内部。
    if _, err := enc.Write(src); err != nil {
        return err
    }

    // Close 先清掉 ASCII85 编码器自己的尾部缓冲。
    if err := enc.Close(); err != nil {
        return err
    }

    // Close 新写入 buf 的内容还在外层,因此最后再 Flush。
    return buf.Flush()
}

如果把 Flush 放在 Close 前面,之后至少还需要再次 Flush 才能把 Close 产生的尾部送出去;直接采用“先内层收尾、再外层刷新”的顺序更不容易遗漏。

ASCII85 Write、nbuf、Close、Flush、错误返回和禁止再次 Write 的生命周期边界结构图
图2:收尾边界结构图,查看 Close、Flush、错误返回与生命周期约束;这不是运行截图。

收尾时还要处理哪些边界

场景应该怎么做原因
尾部不足 4 字节必须调用 enc.Close()让编码器输出最后的短块
外层是 bufio.WriterClose 成功后再调用 Flush()把 Close 新写入的文本推出去
Close 返回错误立即返回或记录错误,不要忽略底层 Writer 可能已经拒绝收尾写入
Close 之后继续 Write视为生命周期错误文档明确不允许在 Close 后继续写

这里的 Close 只结束 ASCII85 编码器本身。若最终 Writer 代表文件、压缩器或网络响应,它们各自仍可能有自己的收尾协议,不能因为 ASCII85 已经关闭就推断整个资源链都已经完成。

回归检查和收尾清单

修复后至少覆盖三组输入:长度刚好是 4 的倍数、长度多出 1~3 个字节、空输入。重点不是比较某一串固定文本,而是确认外层 Writer 在 Close 之后仍能收到尾块,并且 Close 或 Flush 的错误不会被吞掉。

  • 把 ascii85.NewEncoder 当作 io.WriteCloser 管理,不把它当普通 io.Writer。
  • 嵌套缓冲时遵循从内到外的收尾顺序:编码器 Close,外层 Writer Flush。
  • 专门测试最后不足一个 4 字节块的输入,避免“整块输入通过、尾部输入失败”。
  • Close 后不再 Write;如果下游还有关闭动作,继续按各层资源的生命周期处理。

常见问题

ascii85.NewEncoder 有公开的 Flush 方法吗?

没有。它通过返回的 io.WriteCloser 暴露 Close,由 Close 完成编码器尾部收尾;外层 Writer 是否有 Flush 是另一层接口的事情。

为什么先 Flush 再 Close 有时也能得到完整结果?

当输入刚好没有待处理尾块,或者最终 Writer 没有继续缓存时,错误顺序可能暂时不暴露。但只要 Close 产生了新输出,之前的 Flush 已经结束,仍可能留下未送出的内容。

Close 成功后还要不要 Flush?

如果传给 NewEncoder 的就是 bufio.Writer,一般要。Close 只是把尾部写入这个 Writer,Flush 才负责把外层缓冲送到最终目标。

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