Go ascii85.NewEncoder Flush 后为什么还要 Close
来源:17golang原创
时间:2026-11-10 19:13:19 245浏览 收藏
在 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。

旧写法的风险: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 产生的尾部送出去;直接采用“先内层收尾、再外层刷新”的顺序更不容易遗漏。

收尾时还要处理哪些边界
| 场景 | 应该怎么做 | 原因 |
|---|---|---|
| 尾部不足 4 字节 | 必须调用 enc.Close() | 让编码器输出最后的短块 |
外层是 bufio.Writer | Close 成功后再调用 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,外层 WriterFlush。 - 专门测试最后不足一个 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 才负责把外层缓冲送到最终目标。
-
369 收藏
-
344 收藏
-
278 收藏
-
464 收藏
-
327 收藏
-
321 收藏
-
255 收藏
-
230 收藏
-
207 收藏
-
150 收藏
-
490 收藏
-
146 收藏
-
464 收藏
-
352 收藏
-
479 收藏
-
229 收藏
-
322 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习