Golang Gzip压缩与解压(附坑)

3 minute read

Gzip是一种压缩文件格式并且也是一个在类Unix 上的一种文件解压缩的软件,通常指GNU計劃的實現,此處的gzip代表GNU zip。 也經常用來表示gzip這種文件格式。 軟件的作者是Jean-loup Gailly和Mark Adler。

演示

 1package main
 2
 3import (
 4 "bytes"
 5 "compress/gzip"
 6 "fmt"
 7 "io/ioutil"
 8)
 9
10func main() {
11
12 var (
13  result bytes.Buffer
14  data = []byte("test")
15 )
16 
17 // 压缩
18 w := gzip.NewWriter(&result)
19 w.Write(data)
20 //w.Flush()
21 w.Close()
22 fmt.Println("gzip size:", len(result.Bytes()))
23
24 // 解压
25 r, _ := gzip.NewReader(&result)
26 r.Close()
27 undatas, _ := ioutil.ReadAll(r)
28 fmt.Println("ungzip size:", len(undatas))
29}

输出:

1gzip size: 28
2ungzip size: 4

踩到的坑

通常情况下,在实际应用时演示代码中第20行的w.Close()加上defer是没有问题的,但存在有特殊情况。

 1func Compression(data []byte) ([]byte, error) {
 2 var result bytes.Buffer
 3
 4 gz := gzip.NewWriter(&result)
 5    defer gz.Close()
 6
 7 if _, err := gz.Write(data); err != nil {
 8  return nil, err
 9 }
10
11    // 注释掉上面的defer gz.Close(),去除下面行的注释
12    // gz.Close()
13
14 return result.Bytes(), nil
15}

如上示例代码,在调用后没有返回错误的情况下,取其结果进行解压,将会喜获unexpected EOF错误。导致问题的罪魁祸首正是defer,当gz.Close()被执行时会向数据末尾写入EOF结束标志,但我们将gz.Close()放到defer中执行时,由于defer是在return确定返回值之后执行,也就导致gz.Close实际没能将EOF写入数据末尾。

解决方法很简单,不要在defer中调用close即可。

实例

 1package utils
 2
 3import (
 4 "bytes"
 5 "compress/gzip"
 6 "io/ioutil"
 7)
 8
 9func Compression(data []byte) ([]byte, error) {
10 var result bytes.Buffer
11
12 gz := gzip.NewWriter(&result)
13
14 if _, err := gz.Write(data); err != nil {
15  return nil, err
16 }
17
18 if err := gz.Close(); err != nil {
19  return nil, err
20 }
21
22 return result.Bytes(), nil
23}
24
25func UnCompression(data []byte) ([]byte, error) {
26 dataTmp := bytes.NewReader(data)
27
28 r, err := gzip.NewReader(dataTmp)
29 if err != nil {
30  return nil, err
31 }
32 if err = r.Close(); err != nil {
33  return nil, err
34 }
35
36 result, err := ioutil.ReadAll(r)
37 if err != nil {
38  return nil, err
39 }
40
41 return result, nil
42}

附注

进行解压操作时,在gzip.NewReader()之后可以直接调用Close(),它不会关闭底层的io.Reader,不影响后续读取数据。