gzip.Reader读取压缩流时必须显式检查io.EOF,因它不会自动返回;推荐用io.Copy解压,若需逐块读则每次检查n和err;解压tar.gz需正确嵌套gzip.NewReader与tar.NewReader,并严格按Header.Size读取。
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() 来判断流是否结束——它只释放资源,不反映数据边界注意文件路径、权限和错误传播。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.NewReadertar.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: invalid header:文件不是 gzip 格式,或开头被截断(比如 HTTP 响应体含 chunked 编码未解码)gzip: invalid checksum:数据损坏,或写入时未调用 gzip.Writer.Close() 导致尾部 CRC 缺失unexpected EOF:文件不完整,常见于网络下载中断后直接解压gzip: invalid header,需在 gzip.NewReader 前加 fi, _ := f.Stat(); if fi.Size() == 0 { return errors.New("empty gzip file") }
Close()、别跳过 Stat() 检查、别信输入源一定合规。