实测 gzip 各级别需用真实数据(如 2KB–50KB JSON、日志、Protobuf)跑基准,新建 bytes.Buffer 和 gzip.Writer 并调用 w.Close() 获取准确长度;小数据(≤100 字节)时 level=9 反而体积更大,level=1 可能最优;解压失败多因传输截断或未 Close,须用 gunzip -t 验证。

怎么实测 gzip 各级别压缩比和耗时
直接跑基准测试,别靠文档猜。Go 的 gzip 级别是整数 0–9,但实际效果受数据特征影响极大——比如 JSON、Protobuf、纯文本、二进制日志,结果可能差一倍。
关键不是“哪个最快”,而是“在你的数据上,哪一级带来收益拐点”。常见误区是用空字符串或 10 字节测试,结果全是噪声。
- 准备真实样本:取线上 API 响应体(如 2KB–50KB 的 JSON)、日志行集合、或 protobuf 序列化结果,至少 100 条
- 统一写法:用
gzip.NewWriterLevel(w, level),别用SetLevel()—— 后者在 writer 已写入后调用无效 - 每次测试前新建
bytes.Buffer和新gzip.Writer,避免缓冲区残留干扰 - 必须调
w.Close()后再取buf.Bytes(),否则体积不准(漏 trailer 导致解压失败,也影响长度统计)
为什么 BestCompression 不一定压缩率最高
gzip 基于 DEFLATE,而 DEFLATE 对小数据有固定头部开销(约 18–30 字节)。当原始数据 ≤ 100 字节时,gzip.BestCompression(即 level=9)压缩后反而更大——这不是 bug,是算法本质限制。
实测过 87 字节的 JSON:gzip.NoCompression 输出 112 字节,gzip.BestCompression 输出 126 字节。此时 level=1(BestSpeed)反而是最优解(108 字节)。
立即学习“go语言免费学习笔记(深入)”;
- 小数据(
- level=0(
NoCompression)只是加 gzip header,不压缩,但能被所有解压器识别,适合“假装压缩”绕过某些中间件校验 - level=-1(
DefaultCompression)等价于 6,是通用起点,但不适用于高频低延迟场景
压缩比之外必须看的两个指标
只比输出字节数会误判。API 响应体从 10KB 压到 8KB,看似省了 20%,但如果压缩耗时从 50μs 涨到 800μs,QPS 可能掉 30%。
你得同时记下:time.Now() 到 w.Close() 的耗时,以及 buf.Len()。用 go test -bench 最稳,例如:
func BenchmarkGzipLevel6(b *testing.B) {
data := loadSampleData()
for i := 0; i < b.N; i++ {
buf := &bytes.Buffer{}
w, _ := gzip.NewWriterLevel(buf, gzip.DefaultCompression)
w.Write(data)
w.Close() // 必须
}
}
- 别在 benchmark 里用
defer w.Close()—— defer 开销不可忽略,且会掩盖真实 close 时间 - 大文件(>10MB)要加
bufio.NewWriterSize(w, 1<<20),否则默认 4KB 缓冲会让 write 频繁 syscall - 如果目标是 HTTP 响应,记得加上
Content-Encoding: gzipheader,并确认 client 支持该级别(极老 Android WebView 不支持 level=9 流)
gzip 解压失败常见原因和验证方式
“unexpected EOF” 或 “invalid header” 不一定是压缩错了,大概率是传输截断或 Close 漏调。
验证步骤很机械,但有效:
- 先用
gunzip -t检查生成的[]byte是否合法:写入临时文件再跑命令 - 解压代码必须用
gzip.NewReader(bytes.NewReader(compressed)),不能自己切片或跳过前 10 字节 - 网络传输中,确保
io.ReadAll读满整个 body;HTTP/2 流可能分帧,别用io.ReadFull硬读固定长度 - 如果压缩后长度
真正难调的是边界 case:比如 level=9 在某次 GC 后突然变慢,或并发写同一个 gzip.Writer 导致 panic —— 这些不会报错,只会静默损坏数据。


















