压缩比必须用原始字节长度与压缩后实际写出的字节长度之比,需在writer.Close()后获取真实压缩大小,原始长度取len(src),不可依赖os.Stat.Size()或未关闭流的缓冲区长度。

压缩比计算必须用原始字节长度和压缩后字节长度
压缩比不是靠文件系统显示的大小,也不是靠 os.Stat 返回的 Size() 粗略估算——尤其当压缩流未 Close() 时,底层缓冲区数据还没写入,Size() 会严重偏低。真实压缩比只能基于实际参与压缩的原始数据长度与最终写出的压缩字节长度之比。
常见错误是直接拿 len(data) 除以 buf.Len(),但忽略了 gzip.Writer 或 zstd.Encoder 在 Close() 前不会输出尾部校验、头信息等必要字段,导致分母偏小、压缩比虚高。
- 务必在调用
writer.Close()后再读取目标缓冲区长度(如buf.Len()) - 原始数据长度应取压缩前的
len(src),而不是读取文件后的stat.Size()(后者可能含换行符差异、BOM 等干扰) - 若数据来自
io.Reader流(如网络 body),需先完整读入内存再计算,否则无法获知原始长度
gzip、zstd、snappy 的压缩比实测要控制变量
不同算法对同一段数据的压缩比差异极大,但直接对比 gzip.DefaultCompression 和 zstd.EncoderLevel(zstd.SpeedDefault) 没意义——默认级别语义完全不同。必须显式对齐参数维度:
-
gzip:用gzip.BestSpeed(1)、gzip.DefaultCompression(-1)、gzip.BestCompression(9)三档固定测试 -
zstd:对应使用zstd.SpeedFastest、zstd.SpeedDefault、zstd.SpeedBetter(注意:zstd 的 level 是正整数,0~22,但 Go 第三方库如klauspost/compress/zstd封装了语义化常量) -
snappy:无压缩级别,只测单档,但要注意它不保证压缩后一定变小(比如已加密或随机数据会膨胀) - 所有测试必须用相同输入数据、相同 Go 运行环境(GC 关闭/开启)、相同缓冲区复用策略,否则 CPU 缓存、内存分配抖动会干扰结果
文本类数据压缩比高,二进制/加密数据可能负收益
压缩比不是越“高”越好,关键看是否真节省了传输或存储成本。很多开发者忽略一个硬事实:对已经压缩过或加密过的数据再次压缩,大概率会增大体积。
立即学习“go语言免费学习笔记(深入)”;
- JSON/YAML/日志文本:通常压缩比 3:1 ~ 6:1(gzip level=6),zstd 在 level=3 时可达相近比且更快
- JPEG/PNG 图片:再次 gzip 压缩几乎无效,甚至膨胀 0.5%~2%,因为其内部已是 LZ77+熵编码
- AES 加密后的字节流:统计上接近均匀随机分布,任何通用压缩算法都会使其变大(典型 +0.1%~0.3%)
- 小数据块(
别信 benchmark 数字,用真实业务数据跑一遍
文档里写的 “zstd 比 gzip 快 3 倍,压缩比高 15%” 是在特定语料(如 Linux kernel source)下测得的。你的 JSON API 响应体结构、字段重复度、数值分布完全不同。
最可靠的方式是:从线上采样 1000 条典型响应 payload,分别喂给各压缩器,记录每条的 len(src) 和 len(compressed),最后算中位数压缩比(而非平均值,避免长尾异常值干扰)。
容易被忽略的是:zstd 默认启用多线程压缩,而生产环境可能限制了 GOMAXPROCS;snappy 虽快,但它不支持流式 Write 后直接 Read 解压——必须全量写完才能开始解压,这对实时性要求高的链路是硬伤。


















