tar包本身不压缩,压缩比需计算tar+gzip等组合流;直接用os.Stat()会忽略tar对齐开销、gzip头尾18字节及header字段长度;正确做法是在写入时用io.MultiWriter同步统计原始字节数与压缩后字节数。

tar 包本身不压缩,所以没有“tar 包的压缩比”——你真正要算的是 tar + gzip(即 .tar.gz)或 tar + zstd 等组合流的压缩比。 直接对 .tar 文件调用 os.Stat().Size() 除以原始文件总和,会漏掉 tar header 开销(每文件 512 字节对齐);而对 .tar.gz 文件直接除,又混入了 gzip header/trailer 的固定 18 字节。必须拆开测量:原始字节总和 vs 最终写入的压缩流字节数。
为什么不能直接用 os.Stat() 算 .tar.gz 文件大小
常见错误是读取源目录所有文件 os.Stat() 求和,再除以 output.tar.gz 的 os.Stat().Size()。这忽略了三类偏差:
- tar 格式强制 512 字节块对齐:每个文件内容后补 0 到 512 倍数,小文件(如空 config)实际占 1024 字节(header + padding)
- gzip 固定开销:10 字节 header + 8 字节 trailer(CRC32),不管内容多小都存在
- tar header 中
Name、Comment等字段若非空,会额外增加长度(Go 默认为空,但第三方工具可能写入)
正确做法:在写入时同步统计原始与压缩字节数
关键不是“事后查”,而是“写时记”。用 io.MultiWriter 把原始数据同时发给计数器和 tar.Writer,再让 tar.Writer 写入 gzip.Writer,最终落到 bytes.Buffer。示例核心逻辑:
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
gw.Header.Name = "" // 清空可选字段
gw.Header.Comment = ""
tw := tar.NewWriter(gw)
// 原始字节数统计器
var totalInput int64
multi := io.MultiWriter(
io.WriterFunc(func(p []byte) { totalInput += int64(len(p)) }),
tw,
)
for _, file := range files {
hdr, _ := tar.FileInfoHeader(file.info, "")
hdr.Typeflag = tar.TypeReg
tw.WriteHeader(hdr)
io.Copy(multi, file.reader) // 原始内容同时计入 totalInput 和写入 tar 流
}
tw.Close() // 必须先 close tar.Writer
gw.Close() // 再 close gzip.Writer,触发 trailer 写入
ratio := float64(totalInput) / float64(buf.Len())
- 顺序不能错:
tw.Close()必须在gw.Close()之前,否则 tar 尾部 block(两个 512 字节 0)不会写出 -
io.Copy(multi, ...)是关键:它把原始字节流同时喂给计数器和 tar.Writer,避免二次读取 - 别用
gw.Flush()替代gw.Close():Flush 不写 trailer,buf.Len()会偏小
如果输入是磁盘文件,别用 os.Stat().Size() 当原始字节数
文件系统块大小、稀疏文件、符号链接等会导致 os.Stat().Size() ≠ 实际读取字节数。真实原始字节数只能来自 io.Read 过程:
- 对每个文件,用
os.Open()→io.Copy(io.Discard, f)统计真实可读字节数(但效率低) - 更优解:用
io.TeeReader(f, counterWriter),边读边计数,再把该 reader 传给io.Copy(multi, ...) - 特别注意:符号链接默认被
os.Open()跟随,若需跳过,得用os.Lstat()+ 手动判断Mode()&os.ModeSymlink != 0
最易被忽略的一点:tar 包里可能含目录、设备文件、硬链接等非普通文件类型,它们的 Size 字段无意义(如目录为 0),但 tar header 仍占 512 字节。统计原始字节数时,这些条目也要计入 header 开销,不能只加 file.Size()。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











