必须显式调用gzip.writer.close(),否则因crc/isize未写入导致压缩数据损坏;大字符串需流式分块压缩防oom,且每块压缩后立即close并重置缓冲区。

gzip.Writer.Close() 不调用就等于没压缩
大字符串走 pipeline 时,最容易在某一级漏掉 w.Close(),结果下游拿到的 gzip 数据永远解不开。现象很典型:gunzip -t 报 unexpected end of file,Python 的 gzip.open() 直接 panic,Go 解压时抛 gzip: invalid checksum。
根本原因不是数据丢了,而是 gzip 格式尾部的 CRC 和 ISIZE 字段只在 Close() 时写入。哪怕你用 io.Copy 把全部内容都塞进 gzip.Writer,不 Close() 就只是“半成品”。
- 别写完
w.Write()就 return —— 必须紧跟着w.Close()并检查 error -
defer w.Close()只在函数末尾安全;若中间 panic 或提前返回,defer来不及执行 - 压缩失败(如磁盘满、权限不足)时,
w.Close()才会暴露 error;忽略它,等于默许损坏数据流过 pipeline
bytes.Buffer 累积全量压缩数据 = OOM 预告
几百 MB 甚至 GB 级字符串,直接丢给 bytes.Buffer{} + gzip.NewWriter(&buf),十有八九触发 OOM。这不是算法问题,是 bytes.Buffer 底层切片无节制扩容导致的内存爆炸。
真正可行的路径是「分块流式」:把大字符串按固定大小(比如 1MB)切片,每块单独走压缩流程,而不是等全部压缩完再取 buf.Bytes()。
- 每次分块后,显式调用
buf.Reset()清空缓冲区,避免残留旧数据 - 每块压缩完毕立即
w.Close(),释放内部状态,防止字典/窗口状态跨块污染 - 若用
io.CopyBuffer(dst, src, make([]byte, 1024*1024)),可减少系统调用次数,提升吞吐
pipeline 中复用底层 []byte 切片会覆盖前序数据
常见错误是把 bufio.Reader 读出来的 []byte 直接传给 gzip.Writer.Write()。这个切片背后是 reader 的底层 buffer,下一次 Read() 就会被覆盖,导致写入的是脏数据。
必须在每次写入前做一次独立拷贝:
- 用
copy(dst, src)分配新底层数组 - 或直接用
string(src)转成不可变字符串再转回[]byte(适合小块) - 别依赖
scanner.Bytes()返回的切片长期持有——它和 scanner 缓冲共用内存
解压端不套 gzip.NewReader 就等于裸读二进制流
pipeline 下游拿到 .gz 文件或压缩字节流,如果直接用 os.Open() 后接 io.ReadAll(f),得到的是原始 gzip 二进制流,不是解压后的文本。表现为乱码、长度异常、JSON 解析失败等。
正确做法永远是先用 gzip.NewReader() 包一层:
-
r, err := gzip.NewReader(f),然后io.ReadAll(r) - 别跳过
r.Close()—— 它会校验尾部 CRC,发现损坏能早报错 - 若上游是分块压缩,下游必须按块解压,不能把所有块拼一起再解——gzip 不支持多段拼接
最易被忽略的一点:gzip 的状态机是单向的,每个 gzip.Writer 实例只能对应一个完整 gzip 流;跨阶段复用 writer 实例却不 reset,或者在未 close 的 writer 上继续 write,都会让 CRC 和字典状态错乱,且这种错误不会立刻暴露,可能延后几轮 pipeline 才崩。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











