压缩比 = 原始字节数 / 压缩后字节数,保留两位小数;必须基于 gzip.writer.close() 完全刷新后的实际长度计算,原始大小取 os.fileinfo.size(),否则结果虚高或解压失败。

压缩比怎么算:公式和实际值必须一致
压缩比 = 原始字节数 / 压缩后字节数,结果通常保留两位小数。这个比值不是理论值,必须基于 gzip.Writer.Close() 完全刷新后的实际输出长度——漏掉 Close() 会导致压缩数据不完整,计算出的比值虚高,解压时直接失败。
常见错误是用 buf.Len() 在 Close() 前取长度,此时缓冲区里还卡着未 flush 的压缩块;或者用 os.Stat() 查压缩文件大小但没等写入完成(比如异步 goroutine 中未同步)。
- 务必在
gzipWriter.Close()返回后,再读取底层bytes.Buffer或文件的实际长度 - 原始大小要取源文件真实
os.FileInfo.Size(),而非io.Copy返回的字节数(后者可能因读错提前中断) - 对空文件或极短字符串(
gzip 压缩级别对压缩比的影响实测要点
Go 的 gzip.BestCompression(值为 9)确实能拉高比值,但不是线性增长,且代价明显:CPU 时间可能翻倍,而比值只提升 5–12%(取决于文本重复度)。对 JSON 日志类数据有效,对已压缩过的二进制(如 .jpg、.mp4)基本无效,甚至负收益。
实操建议:
- 默认用
gzip.DefaultCompression(-1),平衡速度与压缩比 - 需要极致压缩时,只对纯文本、日志、CSV 等冗余高的数据启用
gzip.BestCompression - 避免在 HTTP 流式响应中设 9 级——首字节延迟显著增加,用户感知卡顿
- 用
benchstat对比不同级别:写个BenchmarkGzipLevelX,固定输入样本,测Bytes()和ns/op
多文件打包场景下压缩比容易误判
用 archive/zip 打包多个文件时,压缩比不能简单用“总原始大小 / zip 文件大小”来算。因为 ZIP 格式本身有额外开销:每个文件头约 30 字节、中央目录、文件名编码、可选 AES 加密字段等。尤其当打包大量小文件(如千个 1KB 文件),ZIP 头部开销可能占最终体积 10% 以上。
更准确的做法:
- 先用
archive/tar打包(无压缩、零元数据开销),再套compress/gzip——此时压缩比反映的是纯 DEFLATE 效果 - 若必须用 ZIP,单独统计每个文件压缩前后的大小,加权平均(按原始大小加权),再对比 ZIP 总大小
- 注意 ZIP 默认不压缩(
zip.FileHeader.Method = zip.Store),要显式设为zip.Deflate,否则比值恒为 1.0
zstd 和 brotli 的压缩比测量陷阱
第三方库如 github.com/klauspost/compress/zstd 提供更高压缩比,但直接替换 gzip 后发现比值没变甚至下降?大概率是没调对参数。zstd 的 zstd.SpeedDefault 级别比 gzip 默认快 3–5 倍,但压缩比略低;而 zstd.SpeedBestCompression 才真正对标 gzip 9 级。
关键点:
- zstd 默认启用
WithWindowSize(1(4MB 窗口),内存占用远高于 gzip(默认 256KB),大文件压缩时易 OOM - brotli 的
github.com/andybalholm/brotli需手动指定Quality(0–11),Quality=11才接近 gzip.BestCompression,但耗时高得多 - 所有第三方压缩器也必须调
Close()或等效 flush 方法,否则长度不准
最常被忽略的是:压缩比数字本身没意义,除非你同时记录 CPU 时间、内存峰值、解压兼容性(比如 zstd 在旧客户端上根本没法解)。拿 gzip 9 级换来的 8% 比值提升,是否值得多花 300ms 和 120MB 内存,得看你的 SLA 和部署环境。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











