必须 Flush() + Close() 才能获取完整压缩数据:Write() 后漏 Flush() 丢校验和元数据,不 Close() 漏 trailer 导致大小偏差 5–20 字节、耗时不准;正确顺序为 Write()→Flush()→Close()。

测压缩前后字节数和耗时,必须 Flush + Close
只调用 gz.Write() 不足以拿到完整压缩数据——未 Flush() 会导致尾部校验、压缩流元数据丢失;不 Close() 则可能漏写 trailer,buf.Len() 偏小,耗时测量也不准。实测中漏掉 Flush() 会让压缩后大小偏差 5–20 字节,对小数据影响显著。
正确顺序是:Write() → Flush() → Close()。三者缺一不可,尤其在性能对比场景下:
-
Flush()确保所有压缩块写入底层io.Writer(如bytes.Buffer) -
Close()写入 gzip trailer 并释放内部状态,否则后续复用会出错 - 计时应包裹整个流程:
start := time.Now()放在Write()前,time.Since(start)放在Close()后
解压性能瓶颈常在 I/O 缓冲,别直接包裸 io.Reader
用 gzip.NewReader(file) 直接包装 *os.File 或 net.Conn,解压吞吐可能不到 20MB/s——因为每次 Read() 只返回几十字节,系统调用爆炸。真实生产环境必须加 bufio.Reader 中转。
推荐写法:gzip.NewReader(bufio.NewReaderSize(src, 64*1024))。64KB 是平衡点:
- 小于 32KB:I/O 次数明显上升,小文件解压延迟翻倍
- 大于 128KB:对多数 SSD/NVMe 场景提升有限,反而浪费内存
- HTTP body 等
io.ReadCloser必须先套bufio,再进gzip.NewReader,顺序不能反
高频解压必须复用 gzip.Reader,且 Reset 要传有效 reader
每解一次就 gzip.NewReader(src),初始化开销占总耗时 30%+,GC 压力陡增。实测高频日志行解压场景下,用 sync.Pool 复用可降 runtime.mallocgc 调用约 45%。
关键约束有两条:
-
sync.Pool的New函数必须返回gzip.NewReader(nil),不能带具体 reader - 从 pool 取出后,必须用
r.Reset(src)绑定新数据源;Reset(nil)是非法的,会 panic - 用完直接
pool.Put(r),不要Reset(nil),内部 buffer 会自动清理
压缩级别影响远不止速度,要按场景选值
gzip.DefaultCompression(-1)是通用平衡点,但实际业务中需权衡:
-
gzip.BestSpeed(1):适合 HTTP 实时响应,压缩比低但 CPU 占用少,延迟敏感场景首选 -
gzip.BestCompression(9):适合日志归档,压缩率高但耗时可能多 3–5 倍,小文件收益不明显 - 中间值(如 4–6):对 JSON/文本类数据性价比最高,压缩比提升明显,耗时增幅可控
注意:设置级别必须在 gz.SetLevel() 后立即调用,且只能在 Write() 前设;写入中途改级别无效。
真正难的是把测量逻辑嵌进真实链路里——比如 HTTP middleware 中同时统计压缩率、耗时、失败率,还要避免干扰原有 error flow 和 context cancel。这些细节不写进 benchmark 就永远发现不了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











