gzip.Reader不能直接测压缩比,因为它只解压数据而不包含gzip头部、校验和、尾部ISIZE等元数据;真实压缩比必须基于原始数据长度与完整.gz文件字节总数之比。

为什么 gzip.Reader 不能直接用来测压缩比
很多人一上来就尝试用 gzip.NewReader 解压再比对长度,结果发现:解压后长度对得上,但压缩比算出来明显偏高(比如原始 1MB,解压后说只有 100KB,但实际磁盘里 .gz 文件是 150KB)。这是因为 gzip.Reader 只负责解压逻辑,不暴露压缩头、校验和、尾部长度等元数据——而这些都占空间。真实压缩比必须基于原始字节流的输入长度和最终 .gz 文件(或字节切片)的总长度。
正确做法:用 gzip.Writer 压缩并捕获输出长度
核心思路是:把原始数据喂给 gzip.Writer,但不写到磁盘,而是写入 bytes.Buffer,然后读它的 Len()。这样得到的是标准 gzip 格式完整字节数,含所有必要开销。
实操要点:
-
gzip.Writer默认使用gzip.DefaultCompression(即 level 6),如需对比不同压缩级别,显式传入gzip.BestSpeed、gzip.BestCompression或gzip.Level(3) - 务必调用
w.Close(),否则尾部 8 字节的 ISIZE 字段(未压缩数据长度)不会写入,导致长度偏小且解压失败 - 避免重复压缩同一数据时复用
gzip.Writer实例——它内部有状态,需每次新建或调用w.Reset()
简短示例:
立即学习“go语言免费学习笔记(深入)”;
var buf bytes.Buffer
w, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
w.Write([]byte("hello world"))
w.Close() // 关键!
ratio := float64(buf.Len()) / float64(len("hello world"))
注意 gzip.Header.Comment 和 Extra 对压缩比的影响
Go 的 gzip.Writer 允许设置自定义 header 字段,比如 Comment 或 Extra。这些内容会写入 gzip 头部,增加最终文件体积,但不影响解压后数据。如果你在测试中启用了 w.Header.Comment = []byte("test"),压缩比会变差——这不是 bug,是规范行为。
常见误操作:
- 忘记清空
w.Header导致历史 comment 残留(gzip.Writer不自动重置 header) - 在 benchmark 中混用带 header 和不带 header 的写法,导致结果不可比
- 用
http.ResponseWriter的 gzip 中间件测压缩比——它可能截断或修改 header,且不暴露底层长度
大文件场景下别用 bytes.Buffer 直接吞
当原始数据超 100MB,bytes.Buffer 会一次性分配大内存,还可能触发 GC 压力。更稳妥的方式是用 io.Pipe 或临时文件 + os.Stat().Size():
- 写入
io.Pipe的 writer 端,从 reader 端边读边计数(适合流式处理) - 写入临时文件后用
os.Stat获取长度,再os.Remove——磁盘 IO 慢但内存可控 - 若需多次复用压缩结果,可缓存
[]byte,但注意gzip.Writer输出不保证可重复解压(因时间戳、OS 字段可能变化)
真正难的不是算比值,而是确保每次测量的输入、压缩参数、header 状态、关闭时机完全一致——少一个条件,结果就不可信。


















