go 的 gzip 包仅支持单文件压缩解压,目录需搭配 tar/zip;必须调用 close() 写入 crc/isize,否则文件损坏;解压须用 gzip.newreader 而非直接读文件;推荐 io.copy 处理流,注意路径、权限、错误处理等周边细节。

Go 的 compress/gzip 包只处理单文件压缩与解压,不打包、不递归、不建目录结构——想压缩整个目录?必须搭配 archive/tar 或 archive/zip。这是绝大多数人写完代码发现生成的 .gz 文件打不开的第一原因。
gzip.Writer 必须显式调用 Close(),否则文件损坏
常见错误现象:gunzip -t file.gz 报 unexpected end of file;或 Python 用 gzip.open() 读取失败。
这是因为 gzip.Writer 内部有缓冲,且 GZIP 格式尾部必须写入 CRC 和 ISIZE 字段,这些只在 Close() 时触发。
- 正确做法:写完
io.Copy(gzWriter, src)后,立刻调用gzWriter.Close();defer gzWriter.Close()是安全写法,但要注意 panic 或提前 return 时是否仍能执行 - 别用
bytes.Buffer直接读取未关闭的 gzip 流——buf.Bytes()拿到的是不完整数据 - 压缩级别可选:
gzip.NoCompression(最快)、gzip.BestSpeed(适合日志)、gzip.DefaultCompression(通用)
解压 .gz 文件必须用 gzip.NewReader,不能 os.Open 后直接读
错误做法:把 *os.File 当作原始字节流直接 io.ReadFull 或 io.ReadAll —— 得到的是乱码二进制,不是解压后内容。
gzip.NewReader 才是专用于解析 GZIP 流的封装,它会跳过 header、校验 magic bytes、处理 deflate 块。
- 务必在函数退出前调用
gr.Close(),释放底层 reader 资源(虽然不关不影响解压结果,但可能泄漏) - 目标文件路径需手动处理:用
strings.TrimSuffix(srcName, ".gz")推荐去掉后缀,os.Create(dst)不会自动建父目录 - 若需保留源文件权限,得从
srcFile.Stat().Mode()提取并传给os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, mode)
解压时遇到 io.EOF 别硬循环,用 io.Copy 最省心
gzip.Reader 不会在流末尾自动返回 io.EOF;你调 Read(p) 可能返回 n > 0, err == nil,下一次才返回 n == 0, err == io.EOF。手写循环容易卡死或漏最后一块。
- 推荐一律用
io.Copy(dst, gr)解压,它内部已正确处理边界和错误中断 - 如需逐块控制(比如限速、进度上报),每次
Read()后必须同时检查n和err:if n == 0 && err == io.EOF或err != nil && err != io.EOF才退出 -
gr.Close()不代表流结束,只是释放资源;不要靠它来判断数据是否读完
gzip.NewReader 初始化失败的典型原因
报错 gzip: invalid header 几乎都指向输入源问题,而非代码逻辑:
- 文件开头被截断(比如 HTTP chunked 编码未完全接收就传给
gzip.NewReader) - 传入的是纯文本或 JSON,不是 GZIP 格式(忘了客户端根本没压缩)
- 读取时用了
os.Open但后续又 seek 过,导致 magic bytes 错位 - HTTP 请求体被中间件双压缩,或服务端错误地对已压缩响应再套一层 gzip
真正难的不是写对那几行代码,而是路径校验、父目录创建、权限继承、错误传播这些“周边动作”——它们不出现在教程里,但一漏就让程序在线上静默失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











