gzip.Reader读取压缩流时必须显式检查io.EOF,因它不会自动返回;推荐用io.Copy解压,若需逐块读则每次检查n和err;解压tar.gz需正确嵌套gzip.NewReader与tar.NewReader,并严格按Header.Size读取。

gzip.Reader 读取压缩流时必须检查 io.EOF
Go 的 gzip.Reader 不会在读完所有数据后自动返回 io.EOF,而是可能在解压末尾仍返回 nil 错误、但后续再读一次才触发 io.EOF。不显式处理会导致循环卡死或漏数据。
- 用
io.Copy直接解压到目标io.Writer是最安全的方式,它内部已正确处理io.EOF - 若需逐块读取(如校验、限速),每次
Read后必须检查返回的n和err:当err == io.EOF或n == 0 && err == nil时停止 - 别依赖
gzip.Reader.Close()来判断流是否结束——它只释放资源,不反映数据边界
解压单个 .gz 文件到磁盘的最小可靠写法
注意文件路径、权限和错误传播。Go 标准库不会自动创建父目录,也不会覆盖只读文件。
- 先用
os.Open打开.gz文件,失败直接返回 - 用
gzip.NewReader包装,记得在函数退出前调用gr.Close() - 目标文件名建议去掉
.gz后缀(可用strings.TrimSuffix(name, ".gz")),并用os.Create创建;若需保留原始权限,得从源文件Stat()中提取Mode() - 用
io.Copy转发数据,它会自动处理缓冲与错误中断
func gunzipFile(src, dst string) error {
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
gr, err := gzip.NewReader(f)
if err != nil {
return err
}
defer gr.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, gr)
return err
}
解压 tar.gz 时别直接用 gzip.NewReader 套 tar.NewReader
tar.gz 是两层封装:外层 gzip、内层 tar。常见错误是先解压整个流到内存再解析 tar,或漏掉 tar.Header.Size 导致读取错位。
- 必须用
gzip.NewReader包装原始*os.File,再把该gzip.Reader传给tar.NewReader -
tar.Reader.Next()每次返回一个文件头,之后必须用io.CopyN或循环读取恰好hdr.Size字节,不能读到io.EOF—— 否则会污染下一个文件头的读取位置 - 目标路径需用
filepath.Clean(hdr.Name)防止../路径遍历,且要提前os.MkdirAll(filepath.Dir(dst), 0755)
gzip.Reader 初始化失败的典型错误信息
遇到这些提示,基本可定位为输入源问题:
立即学习“go语言免费学习笔记(深入)”;
-
gzip: invalid header:文件不是 gzip 格式,或开头被截断(比如 HTTP 响应体含 chunked 编码未解码) -
gzip: invalid checksum:数据损坏,或写入时未调用gzip.Writer.Close()导致尾部 CRC 缺失 -
unexpected EOF:文件不完整,常见于网络下载中断后直接解压 - 空文件或 0 字节文件会触发
gzip: invalid header,需在gzip.NewReader前加fi, _ := f.Stat(); if fi.Size() == 0 { return errors.New("empty gzip file") }
Close()、别跳过 Stat() 检查、别信输入源一定合规。










