压缩比本质是原始大小÷压缩后大小,必须在gzip.Writer.Close()完成后用os.Stat分别获取两文件Size(),否则结果失真;zip多文件需累加各原始文件Size()再除以最终zip大小。

用 os.Stat 获取原始与压缩后文件大小
压缩比本质是「原始大小 ÷ 压缩后大小」,所以第一步必须准确拿到两个数值。Go 中不能靠 log.txt 文件写完就立刻读取压缩结果——得等 gzip 或 zip 写入完成且关闭文件句柄,否则 os.Stat 可能返回 0 或旧 size。
常见错误:在 gzip.Writer.Close() 前调用 os.Stat,导致压缩比恒为 ∞ 或 panic。
- 先写日志到临时文件(如
app.log) - 用
os.Create("app.log.gz")创建压缩目标,套上gzip.NewWriter -
务必等
gzw.Close()返回 nil 后,再对app.log和app.log.gz分别调用os.Stat().Size() - 注意:Windows 下若文件被其他进程占用(比如 tail -f),
os.Stat可能报access denied,需加重试或跳过
用 archive/zip 打包多日志时,压缩比要按总原始大小算
单个 .log 压缩和打包多个日志(如 app-2024-01-01.log, app-2024-01-02.log)进一个 .zip,计算逻辑不同:前者是 1:1,后者得把所有待打包文件的 Size() 加总,再除以最终 .zip 文件大小。
容易踩的坑是误把 zip 中某个文件的 File.FileInfo().Size() 当作原始大小——那是压缩后的内部大小,不可信;必须用原始磁盘文件的 os.Stat 结果。
立即学习“go语言免费学习笔记(深入)”;
- 遍历日志文件列表,用
os.Stat(path).Size()累加得到totalOriginalSize - 用
zip.Writer写入所有文件,zw.Close()后再os.Stat(zipPath).Size() - 如果 zip 包含目录结构或空文件夹,它们不占原始字节数,但 zip 文件本身会略大,压缩比会轻微偏低——属正常现象
避免用 runtime.MemStats 或内存估算替代磁盘文件大小
有人想省 IO,试图用日志写入时的内存 buffer 长度当原始大小,或用 gzip.Reader 解压后读取长度反推——这不可靠。原因有三:日志可能含换行符归一化、gzip header 开销、zip 有 central directory 等元数据。
真实压缩比只对落地到磁盘的文件有意义。线上服务若需实时反馈,建议异步统计:日志轮转后触发 goroutine 调用 os.Stat,结果发到 metrics(如 Prometheus 的 log_compress_ratio gauge)。
- 不要用
bytes.Buffer.Len()代替os.Stat().Size() - 不要在压缩流未
Close()前读gzip.Header估算 - 若日志文件极大(>1GB),
os.Stat很快,但要注意int64溢出风险——Go 的os.FileInfo.Size()返回int64,一般够用;超 8EB 才溢出,基本不用管
压缩比低于 1.0 说明什么?
理论上压缩比应 >1.0(原始比压缩后大)。如果算出来 compressRatio ,大概率是原始文件路径填错,比如误传了 <code>app.log.gz 给原始大小变量,或者压缩目标路径和原始路径指向同一个文件(覆盖写入失败,但文件 size 没变)。
另一个可能是日志内容极度稀疏或已高度压缩(比如里面全是 base64 编码的图片),gzip 对这类数据负向压缩——此时比值可能接近 0.95,属于 gzip 限制,不是代码 bug。
- 加一行校验:
if originalSize - 打印两文件的绝对路径,确认没拼错字符串(尤其注意
filepath.Join和硬编码斜杠混用) - 用命令行手动验证:
ls -lh app.log app.log.gz和go run calc.go输出是否一致
Close() 后再 Stat,原始大小一定来自磁盘而非内存 buffer。其余都是路径、权限、类型判断的细节问题。


















