必须调用 gzip.writer.close() 才能获取准确压缩长度,压缩后长度为 buf.len(),原始长度为 len(data),未关闭则结果偏小。

用 gzip.Reader 和 bytes.Buffer 获取原始与压缩后字节长度
Go 里没法直接从 gzip.Writer 拿到压缩后长度,必须先写入缓冲区再读取。核心思路是:把原始数据写进 gzip.Writer,再从底层 bytes.Buffer 取出压缩后的字节切片。
常见错误是直接对压缩流调用 Len() —— 这返回的是未压缩前写入的字节数,不是 gzip 编码后的实际大小。
实操建议:
- 用
bytes.NewBuffer(nil)创建缓冲区,传给gzip.NewWriter - 写完后调用
writer.Close()(否则压缩头尾不完整,Buffer.Bytes()结果偏小) - 压缩后长度 =
buffer.Len();原始长度 =len(originalData)
data := []byte("hello world hello world hello world")
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
gz.Write(data)
gz.Close() // 必须关闭,否则压缩不完整
ratio := float64(buf.Len()) / float64(len(data))
为什么不能用 compress/gzip 的 Reader 反向计算压缩率
gzip.Reader 是为解压设计的,它会跳过 gzip header、校验和等元信息,只暴露解压后内容。你无法用它“倒推”压缩后体积——它根本不暴露压缩流本身。
有人尝试用 io.Copy(ioutil.Discard, reader) 看耗时或间接估算,但这是无效的:它只说明解压快慢,和压缩率无关。
关键点:
-
gzip.Reader输入必须是合法 gzip 流,不能用来测量压缩效果 - 若原始数据本身含 gzip header(比如误把已压缩数据再压一次),
gzip.Reader会解压成功,但你的压缩率计算就完全失真 - 压缩率必须基于原始输入字节 vs. 实际写出的 gzip 字节,二者都得可控可测
不同 gzip.NewWriterLevel 对压缩率的影响很显著
Go 默认用 gzip.DefaultCompression(= 6),但如果你传 gzip.NoCompression(= 0)或 gzip.BestCompression(= 9),结果差异可达 2–3 倍。别假设“用了 gzip 就一定压缩”,level=0 实际上只是打包,几乎不压缩。
实操建议:
- 测试时显式指定 level,例如
gzip.NewWriterLevel(&buf, gzip.BestSpeed) - 小文本(BestSpeed 可能反而更大(gzip header 占比高),要实测
- 注意
gzip.BestCompression在 Go 1.22+ 中可能触发更激进的 Huffman 优化,但 CPU 开销明显上升
注意 gzip.Writer 的隐式 flush 和 header 写入时机
如果你没调用 Close(),而用 Flush(),压缩流不完整:header 已写,但 footer(ISIZE、CRC32)缺失,导致最终字节数偏小且无法被标准 gzip 工具识别。
另一个坑是重复 Close:第二次调用会 panic,报 close of closed channel(内部用 channel 同步 flush)。
安全做法:
- 务必在写完后调用一次
Close(),不要用Flush()替代 - 如果需要多次写入并分段获取压缩长度,每次都要新建
gzip.Writer+bytes.Buffer - 生产中若需流式压缩率监控,建议封装一个带计数器的
io.Writer,包装在gzip.Writer外层,而不是依赖 buffer.Len()
压缩率数字本身容易误导——它只反映字节减少比例,不反映解压开销或内存占用。真实服务中,level=1 和 level=9 的吞吐差异可能比压缩率差异更重要。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











