zip.Writer 默认使用 zip.Store(无压缩),需显式设 header.Method = zip.Deflate 才启用压缩;gzip.Writer 仅压缩单文件,不打包,故 ZIP 体积不变或 .gz 解压报错多因混淆二者。

zip.Writer 默认不压缩,gzip.Writer 不打包 —— 这是 Go 压缩逻辑最常被误解的起点。你写完代码发现 ZIP 体积没变小、.gz 解压报 unexpected end of file,大概率卡在这两个点上。
为什么 zip.Writer 生成的文件没压缩?
Go 的 archive/zip 包默认用 zip.Store(零压缩归档),不是 zip.Deflate。哪怕你调了 CreateHeader 或 FileInfoHeader,也不会自动切换模式。
- 每个文件写入前必须显式设置:
header.Method = zip.Deflate -
zip.Deflate是标准库唯一支持的压缩方法;zip.BestCompression等常量根本不存在 - 空文件或极小文件(Store,属正常行为,不是 bug
- 已压缩格式(
.jpg、.png、.mp4)再压基本无效,甚至略增体积;文本类(.go、.json、.log)才真正受益
为什么 gzip.Writer.Close() 不能省略?
compress/gzip 是流式压缩器,只处理单个数据流,不负责打包多个文件。它输出的是标准 GZIP 格式,而该格式强制要求末尾写入 CRC32 校验值和原始长度(ISIZE)字段 —— 这些只在 Close() 时落盘。
- 仅调
Write()或Flush()无法生成可解压的完整文件,gunzip -t会报unexpected end of file -
defer gz.Close()安全,但得确保它在所有return路径前执行;比如if err != nil { return err }后没 defer 就漏关 -
gz.Close()返回error,不能忽略:磁盘满、权限不足等失败会在这里暴露 - 想打包多个文件?必须先用
archive/tar构建 tar 流,再套gzip.NewWriter,最终生成archive.tar.gz
中文文件名乱码、Zip Slip 漏洞怎么防?
Windows 资源管理器默认用 GBK 解码文件名,而 Go 写 ZIP 默认 UTF-8,不设标志就显示 ???.txt;解压路径不做校验则可能覆盖系统文件。
- 中文名必须设:
header.Flags = 1(表示文件名是 UTF-8 编码);Go 1.22+ 可用更清晰的header.SetUTF8(true) - 目录打包时路径要用
filepath.ToSlash(filepath.Rel(root, absPath))转成相对路径,避免绝对路径(如/home/user/a.txt)导致解压工具静默失败 - 解压时必须校验:
cleanName := filepath.Clean(f.Name),再检查cleanName != f.Name、是否含".."或开头为"/",最后确认filepath.ToSlash(dstPath)是否以目标根目录开头 - 别忘了
header.SetModTime(time.Now()),否则所有文件时间戳一样,看着就很假
Close() 是格式完整性守门人,Flags=1 和路径清理是安全底线。漏掉任意一个,生成的文件就只是“看起来像”,而不是“能用”。


















