gzip比zlib略大(约18字节),因含头尾和CRC校验;zstd通常压缩比更高,尤其对日志、JSON等文本数据,需关闭校验、设单线程并正确close以准确对比。

用 compress/gzip 和 compress/zlib 做基础压缩比对比
Go 标准库自带的 gzip 和 zlib 是最常被拿来横向比较的两个包,它们底层都基于 DEFLATE,但封装协议不同:前者加了 GZIP 头尾和 CRC 校验,后者是原始 DEFLATE 流。这意味着同一批数据,gzip 输出必然略大(多 18 字节左右头尾),但解压兼容性更好。
实操时别直接拿 bytes.Buffer 写完就比长度——要确保输入完全一致、压缩级别相同、且排除缓冲区预分配干扰:
- 统一用
bytes.NewReader(data)构造输入源,避免切片底层数组复用影响 - 压缩级别统一设为
gzip.BestCompression或zlib.BestCompression(注意两者常量值相同,但属于不同包) - 写入目标用
bytes.NewBuffer(nil),不预设容量,避免隐式填充
引入 github.com/klauspost/compress 测试更高压缩率算法
标准库不支持 zstd、lz4 或 snappy,而这些在真实场景中常因速度/比率权衡被选用。第三方库 github.com/klauspost/compress 提供了生产级 zstd 实现,压缩比通常显著优于 gzip,尤其对日志、JSON、文本类数据。
注意几个关键点:
立即学习“go语言免费学习笔记(深入)”;
-
zstd.Encoder默认启用帧校验和(WithChecksum(true)),会增加约 4 字节开销;若只比纯压缩能力,应显式关掉 - 它的
WithConcurrency(1)可禁用多 goroutine,保证单线程行为可复现,避免 CPU 调度抖动影响耗时统计 - 别漏掉
encoder.Close(),否则最后 few bytes 可能滞留在内部 buffer 中未写出
避免因数据特征导致的压缩比误判
压缩比不是固定值,严重依赖输入数据的熵值和重复模式。拿一段全零字节或随机加密数据去测,所有算法都会接近 1:1,结果毫无参考价值。
建议按实际使用场景选测试样本:
- Web 日志:用 Nginx access log 抽样 1MB,含大量重复路径、UA、状态码
- 结构化数据:生成 10 万行 JSON(字段名固定、值有规律),比纯随机字符串更有意义
- 二进制数据:如 protobuf 序列化后的 payload,注意它本身已带一定压缩倾向,再压可能增益很小
另外,Go 的 compress/* 包默认不处理小数据(
输出结果时记得记录压缩耗时与内存分配
只看压缩后字节长度容易误导。比如 zstd 可能比 gzip 少 15%,但耗时翻 3 倍,是否值得取决于你的延迟敏感度。
用 testing.Benchmark 是最稳妥的方式,它自动跑多次并给出平均耗时、内存分配次数(b.ReportAllocs())和每次分配大小:
func BenchmarkGzipCompress(b *testing.B) {
data := loadSampleData()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
gw.Write(data)
gw.Close() // 必须调用
}
}
注意:别在循环外创建 gzip.Writer 并反复 Reset(),因为 Reset(io.Writer) 不重置内部哈希状态,会导致后续压缩结果异常(尤其是小数据多次复用时)。
真正难控的是 GC 对内存统计的干扰——如果测试数据太大(>10MB),建议用 runtime.GC() 在每次迭代前手动触发,或改用 pprof 抓取单次运行的精确 allocs。


















