压缩比需手动计算为原始长度÷压缩后长度,关键是在gzip.Writer.Close()后用bytes.Buffer.Len()获取真实压缩长度,避免因未写入footer导致解压失败或长度偏小。

用 len() 和 io.Copy 获取原始与压缩后字节长度
压缩比不是库自动返回的指标,必须手动计算:原始数据长度 ÷ 压缩后数据长度。关键在于准确捕获两个长度值,且不能因缓冲未刷新导致压缩数据截断。
常见错误是直接对 bytes.Buffer 调用 Len() 但没调用 gzip.Writer.Close(),此时尾部校验头(gzip footer)未写入,长度偏小,解压会失败。
- 原始长度:对输入
[]byte或文件用os.Stat().Size(),或读取时累计io.Copy返回值 - 压缩后长度:必须在
gzip.Writer.Close()之后,再对底层bytes.Buffer调用Len() - 避免用
buf.Bytes()后再取长度——它返回底层数组容量,可能含未使用空间;buf.Len()才是真实写入字节数
统计单次压缩操作的压缩比(内存字符串场景)
对字符串或小块内存数据做压缩比测算,推荐用 bytes.Buffer 作目标写入器,全程不碰磁盘,快且可控。
示例代码中容易漏掉 defer gz.Close() 或误写成 gz.Flush() ——后者不写 footer,压缩流不完整,后续解压必然报 gzip: invalid checksum 或 unexpected EOF。
立即学习“go语言免费学习笔记(深入)”;
func compressRatio(data string) (float64, error) {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
defer gz.Close() // 必须 defer,且是 Close,不是 Flush
_, err := gz.Write([]byte(data))
if err != nil {
return 0, err
}
// Close 刷新所有数据并写入 footer
if err := gz.Close(); err != nil {
return 0, err
}
original := len(data)
compressed := buf.Len()
if compressed == 0 {
return 0, errors.New("compressed size is zero")
}
return float64(original) / float64(compressed), nil
}
统计大文件压缩比(流式处理场景)
对 GB/TB 级文件,不能全载入内存,需靠 os.Stat() 拿原始大小,再用 io.Copy 将压缩流导向临时 bytes.Buffer 或直接写磁盘并记录最终文件大小。
若写磁盘,注意:压缩文件必须 Close() 后再调用 os.Stat(),否则系统缓存可能返回旧 size;若用 bytes.Buffer,仍要等 gzip.Writer.Close() 完成。
- 原始大小:
info, _ := input.Stat(); original := info.Size() - 压缩后大小:写入
output *os.File后,output.Close()再os.Stat(output.Name()).Size() - 更稳妥方式:用
io.MultiWriter同时写入磁盘和计数器(如自定义io.Writer实现只计数),避免二次 stat
不同压缩级别对压缩比的影响必须实测
gzip.DefaultCompression、gzip.BestSpeed、gzip.BestCompression 的数值分别是 -1、1、9,但它们对同一份数据产生的压缩比差异无法预测——取决于数据冗余度。日志文本可能在 level=6 时就逼近极限,而随机二进制数据即使 level=9 也几乎不压缩。
别依赖文档里的“理论压缩比”,真实业务数据必须跑 benchmark:
- 用相同输入,循环测试 level=1 到 9,记录每次
buf.Len() - 注意:
gzip.NewWriter每次都要新建,不能复用已关闭的 writer - level=-1(Default)通常是平衡点,但加密日志、序列化 protobuf 等低冗余数据,level=1 反而更优(压缩比接近,速度快几倍)
最易被忽略的一点:压缩比数字本身没有意义,要看它对应的实际耗时和 CPU 占用。一个压缩比 3.2 的结果,如果耗时 8 秒、占满 1 个核,可能不如压缩比 2.7、耗时 0.3 秒的方案实用。


















