os.Stat 不能直接算压缩比,因其仅返回文件元数据大小,未考虑压缩算法开销、格式差异及压缩级别影响;可靠压缩比须基于实际压缩后字节长度,如用 bytes.Buffer 暂存 zip/gzip 输出并调用 Close() 后取 len。

为什么 os.Stat 不能直接算压缩比
压缩比是原始文件大小除以压缩后文件大小,但很多人误以为调用 os.Stat 获取两个路径的 Size() 就完事了。问题在于:没考虑压缩算法本身的开销(比如 ZIP 的目录结构、头部信息)、不同归档格式对空文件/小文件的处理差异,以及是否启用压缩(如 gzip 的 Level 参数为 gzip.NoCompression 时大小可能反而变大)。
真正可靠的压缩比必须基于实际生成的压缩文件字节长度,而不是理论或元数据估算。
用 archive/zip 压缩并计算压缩比的最小可行步骤
Go 标准库不提供“一键压缩比”函数,需手动控制写入过程并捕获最终字节数。关键点是:不要先写到磁盘再读取大小,而应使用 bytes.Buffer 暂存 ZIP 数据,避免 I/O 开销和临时文件残留。
- 用
bytes.NewBuffer(nil)创建缓冲区,传给zip.NewWriter - 遍历待压缩文件,对每个
*os.File调用file.Stat()得到原始大小(累加) - 用
writer.Create()和io.Copy()写入内容,最后务必调用writer.Close()—— 否则 ZIP 结尾目录未写入,buf.Len()会严重偏低 - 压缩后大小 =
buf.Len();原始大小 = 所有输入文件Stat().Size()之和
buf := bytes.NewBuffer(nil) zw := zip.NewWriter(buf) // ... 添加文件 zw.Close() // 必须! ratio := float64(originalSize) / float64(buf.Len())
对比 compress/gzip 单文件场景下的注意事项
如果只压一个文件(比如日志),用 compress/gzip 更轻量,但压缩比数值会和 ZIP 不可比——因为 GZIP 没有文件名、权限等元数据,纯内容流压缩。此时原始大小仍是源文件 Size(),压缩后大小是 gzip.Writer 写完并 Close() 后底层 bytes.Buffer 的长度。
立即学习“go语言免费学习笔记(深入)”;
-
gzip.NewWriterLevel(buf, gzip.BestCompression)的 Level 影响结果:默认DefaultCompression(-1)和BestSpeed(-2)可能导致压缩比明显下降 - 注意:GZIP 不支持多文件,若强行塞多个
io.Copy到同一个gzip.Writer,解压时只能拿到第一个文件 - 若源文件已压缩(如 JPG、MP4),GZIP 可能使其略微变大,此时压缩比
容易被忽略的边界情况
压缩比不是越高压越好,工程中更关注“可控性”和“可复现性”。以下几点常被跳过但直接影响结果可信度:
- 原始大小必须排除符号链接本身(用
os.Lstat判断是否为 symlink,若是,应跳过或按目标文件统计) - ZIP 中重复文件名会导致后写入的覆盖前一个,原始大小仍应累加所有输入,但压缩后大小只反映最终归档内容
- Windows 下路径分隔符(
\)写入 ZIP 时建议统一转为/,否则某些解压工具可能创建嵌套目录,间接影响压缩率 - 测试时别用 0 字节文件——很多压缩器对其特殊处理(如 ZIP 直接存储不压缩),导致压缩比显示为 ∞ 或 panic
真实项目里,压缩比只是辅助指标;更关键的是验证解压后文件 sha256sum 是否与源一致,以及内存/CPU 消耗是否在预期范围内。


















