必须调用 gz.Close() 才能得到合法 gzip 流;压缩后大小须在 Close() 后取 buf.Len(),否则可能为未压缩数据或不完整流;解压报 unexpected EOF 通常因未 Close()。

必须用 bytes.Buffer 捕获压缩输出,不能直接读底层 buffer
很多人测出来压缩后字节数和原始一样,甚至更小——其实是忘了调用 gz.Close(),导致 buf.Bytes() 拿到的是未压缩的原始输入或不完整数据。gzip.Writer 是缓冲写入器,Write() 只进内部 buffer,Flush() 仅推压缩块,Close() 才写尾部(CRC32 + ISIZE),缺了就不是合法 gzip 流。
- 用
bytes.Buffer作为底层 writer,别用strings.Builder或自己管理切片 - 压缩完必须显式调用
gz.Close();defer gz.Close()可以,但得确保所有Write()已完成,中间 error 早于写入会导致漏压缩 - 别依赖
buf.Len()在Close()前取值——此时长度不可信 - 解压时若报
unexpected EOF,大概率是压缩端没Close(),校验 trailer 失败
测量字符串压缩前后字节数和耗时的最小可靠模板
对 JSON 或其他文本做 API 压缩评估时,要同时拿到原始大小、压缩后大小、压缩耗时、解压缩耗时。关键不是“能不能压”,而是“值不值得压”。
-
inputBytes := []byte(input)—— 原始字节数用len(inputBytes),不是len(input)(Unicode 字符可能占多字节) - 压缩阶段:用
time.Now()包住gz.Write()+gz.Close(),gz.Flush()不必单独计时,它已包含在Close()中 - 压缩后大小:只在
gz.Close()成功后取buf.Len(),这是最终合法 gzip 流长度 - 解压阶段:用
bytes.NewReader(compressed)构造 reader,再传给gzip.NewReader(),最后用io.Copy(io.Discard, gzReader)模拟解压(避免分配大内存),耗时从NewReader()开始算到Copy()结束
compress/gzip 的压缩级别影响实际大小,但不改变测量逻辑
gzip.NewWriterLevel 支持从 gzip.DefaultCompression 到 gzip.BestCompression,数值越大压缩率越高、CPU 越高。但无论选哪一级,测量方式都一样:原始字节数不变,压缩后字节数和耗时会变,解压耗时基本稳定。
- 测试时建议固定用
gzip.BestCompression和gzip.BestSpeed对比,看业务能否接受速度换体积 - 注意:空字符串或极短内容(如
"a")压缩后可能反而更大(gzip header 占约 18–20 字节),这时测量结果会显示compressedSize > originalSize,属正常现象 - 不要在循环里反复 new
gzip.Writer——复用并重置bytes.Buffer更高效,但每次仍需新gzip.NewWriter
文件级压缩大小对比要先确认是否为合法 gzip 流
如果你拿一个 .gz 文件去测,别直接 os.Stat().Size() 就当压缩后大小——得验证它真能被 gzip.NewReader 正确打开。有些“压缩失败但文件已生成”的情况,文件有内容却无法解压。
- 用
os.Open打开 .gz 文件,传给gzip.NewReader,如果返回 error,说明文件损坏或根本不是 gzip 格式 - 合法 gzip 文件的最小长度约 18 字节(header 10 字节 + trailer 8 字节),低于此值可直接判无效
- 想对比原始文件和 .gz 文件大小,原始文件用
os.Stat().Size(),.gz 文件也用os.Stat().Size(),但前提是它通过了gzip.NewReader初始化校验 - 批量处理时,建议加一层 wrapper 函数,先校验再返回 size,避免后续解压环节崩溃
最易被忽略的一点:压缩后字节数 ≠ 网络传输字节数。HTTP gzip 响应还带 header、chunked 编码、TLS record 开销,真实节省得在抓包里看。代码里测出的只是纯压缩层收益。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











