Golang Gzip压缩与解压(附坑)

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

演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
 "bytes"
 "compress/gzip"
 "fmt"
 "io/ioutil"
)

func main() {

 var (
  result bytes.Buffer
  data = []byte("test")
 )
 
 // 压缩
 w := gzip.NewWriter(&result)
 w.Write(data)
 //w.Flush()
 w.Close()
 fmt.Println("gzip size:", len(result.Bytes()))

 // 解压
 r, _ := gzip.NewReader(&result)
 r.Close()
 undatas, _ := ioutil.ReadAll(r)
 fmt.Println("ungzip size:", len(undatas))
}

输出:

1
2
gzip size: 28
ungzip size: 4

踩到的坑

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func Compression(data []byte) ([]byte, error) {
 var result bytes.Buffer

 gz := gzip.NewWriter(&result)
    defer gz.Close()

 if _, err := gz.Write(data); err != nil {
  return nil, err
 }

    // 注释掉上面的defer gz.Close(),去除下面行的注释
    // gz.Close()

 return result.Bytes(), nil
}

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

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

实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package utils

import (
 "bytes"
 "compress/gzip"
 "io/ioutil"
)

func Compression(data []byte) ([]byte, error) {
 var result bytes.Buffer

 gz := gzip.NewWriter(&result)

 if _, err := gz.Write(data); err != nil {
  return nil, err
 }

 if err := gz.Close(); err != nil {
  return nil, err
 }

 return result.Bytes(), nil
}

func UnCompression(data []byte) ([]byte, error) {
 dataTmp := bytes.NewReader(data)

 r, err := gzip.NewReader(dataTmp)
 if err != nil {
  return nil, err
 }
 if err = r.Close(); err != nil {
  return nil, err
 }

 result, err := ioutil.ReadAll(r)
 if err != nil {
  return nil, err
 }

 return result, nil
}

附注

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