必须手动计算float64(len(src))/float64(len(compressed)),因gzip/zlib需Close()或Flush()才生成完整流(含header/trailer),仅Write()会导致缓冲未刷新、校验缺失、压缩比虚高;JSON须先compact再压,避免缩进干扰基准。

必须手动计算 float64(len(src)) / float64(len(compressed)),不能依赖任何库函数返回“压缩比”,且 compressed 必须来自完整写出的字节流(gzip.Writer.Close() 或 zlib.Writer.Flush() 后获取)。
为什么直接 len(src) / len(gzip.NewWriter(...).Write(...)) 不行
常见错误是只调用 Write() 就立刻读 bytes.Buffer.Len()。此时 gzip.Writer 还在内部缓冲,没真正压缩,buf 里可能空或只有原始字节。更糟的是,忘了写尾部校验(CRC32 + ISIZE),导致解压失败——但你算出来的“压缩比”却虚高甚至为 1.0。
-
gzip.Writer必须调用Close()才完成整个 gzip 流(header + compressed blocks + trailer) -
zlib.Writer没有Close(),只能用Flush();漏掉它,Adler32 校验尾部缺失,协议不完整 - 用
defer gz.Close()在函数开头写?万一中间Write()出错提前 return,Close()就没执行
gzip 和 zlib 的压缩比不能混着比
同样一段 []byte,用 compress/gzip 和 compress/zlib 压缩后长度不同,不是算法差异,是协议头尾开销不同:
- gzip:固定 10 字节 header + 8 字节 trailer(CRC32 + ISIZE)
- zlib:2 字节 header + 4 字节 trailer(Adler32)
- 对小数据(1MB 数据,两者比值基本一致
- 别拿
gzip.NewReader(bytes.NewReader(zlibOutput))去解——协议不兼容,直接 panic
字符串压缩前要先转紧凑字节,别信 json.Marshal 默认输出
如果你压的是 JSON 字符串,json.Marshal(v) 默认带缩进和换行,原始字节数虚高,算出来的压缩比偏低 15%–30%。这不是压缩算法问题,是基准错了。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:先用
json.Compact(&buf, rawJSONBytes)得到紧凑格式,再压 - 或者用
json.NewEncoder(ioutil.Discard).Encode(v)避免中间分配,但要注意它自带换行 - 别对
string直接调len()——它返回的是字节长度,不是 rune 数;但压缩比本来就算字节,这点反而是对的 - 如果源是含中文、emoji 的字符串,确保你传给
gzip.Write()的是[]byte(s),不是误用unsafe.String或其他绕过 UTF-8 验证的 trick
实时显示压缩比时最常踩的坑
想边压边打印 “已压 12KB,当前压缩比 4.2×”,结果数字跳变剧烈甚至出现负值,问题几乎都出在统计时机和实例复用上:
- 不要每次
Write()都新建一个gzip.NewWriter(&buf)——压缩上下文重置,DEFLATE 状态丢失,首块永远像在“冷启动” - 原始字节数得用独立计数器(比如自定义
counter struct{ n int }实现Write(p []byte)),不能只靠len(input),因为输入可能是流 - 压缩后字节数必须取自同一个
bytes.Buffer,且每次更新后立刻调buf.Len(),别缓存旧值 -
zstd库的EncodedSize()是估算值,别当真;可靠的是zstd.Encoder.Write()返回的实际写入字节数
压缩比本身不解决业务问题,它只是个诊断信号。真正关键的是:你对比的是否是同一份原始数据?压缩后是否能 100% 解回?尾部校验有没有被忽略?这些细节一错,数字再好看也没用。


















