应先用 bytes.HasPrefix(data, []byte{0x1f, 0x8b}) 校验 gzip 头,再通过 bytes.NewReader(data) 构造 io.Reader 传入 gzip.NewReader;解压后须调用 zr.Close() 防 goroutine 泄漏;读取时据数据大小选 io.ReadAll 或 bufio.Scanner。

gzip.NewReader 解压字节流时 panic: invalid header
直接对未校验的 []byte 调用 gzip.NewReader 很容易 panic,因为该函数要求输入必须是合法 gzip 格式(含 magic bytes 1f 8b),而你传进去的可能是纯文本、JSON 或其他二进制数据。
实操建议:
立即学习 “go 语言免费学习笔记(深入)”;
- 先用
bytes.HasPrefix(data, []byte{0x1f, 0x8b})检查是否为 gzip 数据,再决定是否解压 - 不要把
io.ReadCloser和[]byte混用:传给gzip.NewReader的是io.Reader,不是原始字节切片;需用bytes.NewReader(data)包一层 - 解压后记得调用
zr.Close(),否则可能泄漏 goroutine(尤其在高并发 HTTP handler 中)
解压后读取全部内容用 io.ReadAll 还是 bufio.Scanner
解压结果是 io.Reader,后续怎么读取决于数据性质和内存控制需求。用错方式会导致 OOM 或截断。
实操建议:
立即学习 “go 语言免费学习笔记(深入)”;
- 确定数据大小可控(比如 HTTP 响应体 io.ReadAll(zr)
- 若数据可能很大(如日志流、文件上传体),或只需逐行 / 分块处理,改用
bufio.NewReader(zr)+ReadString('n')或Read循环 - 避免用
strings.NewReader(string(data))再套 gzip —— 这会强制 UTF-8 解码,破坏二进制数据
HTTP 响应体自动解压失败:gzip.NewReader 返回 nil
Go 的 http.Client 默认不自动解压 Content-Encoding: gzip,你得自己处理。但手动解压时若忽略 Content-Length 和 Transfer-Encoding 差异,会读不完或卡住。
实操建议:
立即学习 “go 语言免费学习笔记(深入)”;
- 检查响应头:
resp.Header.Get("Content-Encoding") == "gzip",再决定是否解压 - 用
gzip.NewReader(resp.Body)替代直接读resp.Body,解压后仍要defer zr.Close() - 别假设
Content-Length是解压后长度 —— 它是压缩后字节数,解压后长度未知,别用它预分配切片
compress/gzip 解压性能差?别用 ReadAll + string() 做中间转换
高频解压场景下,反复做 string(bytes) 和 []byte(string) 转换会触发大量内存分配,GC 压力明显上升。
实操建议:
立即学习 “go 语言免费学习笔记(深入)”;
- 如果下游接受
[]byte(比如 JSON 解析器、数据库写入),解压后直接传buf,别转成string - 用
bytes.Buffer配合zr.WriteTo(&buf)比io.ReadAll略快(内部用了更大 buffer) - 注意
gzip.Reader默认 buffer 是 32KB,小数据包下影响不大;大数据可考虑复用gzip.Reader实例(需调用Reset),但要注意并发安全 —— 它不是线程安全的
gzip 解压真正的坑不在 API 调用,而在边界判断和资源生命周期管理。特别是 HTTP 场景下,Body 是否已关闭、是否被多次读取、是否漏掉 Close,这些比“怎么解”更常导致问题。