archive一般用于打包,compress一般用于压缩。

bzip2

bzip2的简介

bzip2包实现bzip2的解压缩,bzip2是对单个文件进行压缩,可以先进行tar归档,然后进行压缩。

bzip2的使用

go标准库中提供了一个对bzip2压缩包进行读取的操作,但是并没有提供进行bzip2压缩操作。

package main

import (
    "compress/bzip2"
    "os"
    "log"
    "fmt"
)

func main() {
    fz, err := os.Open("1.go.bz2")
    if err != nil {
        log.Fatal(err)
    }
    w := bzip2.NewReader(fz)
    buf := make([]byte, 1024 * 100)
    for {
        n, err := w.Read(buf)
        if n == 0 || err != nil {
            break
        }
        fmt.Println(string(buf[:n]))
    }
}

deflate

DEFLATE是同时使用了LZ77算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法。它最初是由菲尔·卡茨(Phil Katz)为他的PKZIP软件第二版所定义的,后来被RFC 1951标准化。很多压缩方式都是在这个基础上封装开发的。

const (
    NoCompression = 0        // 不压缩
    BestSpeed          = 1        // 最快速度压缩
    BestCompression     = 9   // 最佳压缩比压缩
    DefaultCompression = -1  // 默认压缩
)

NewReader

func NewReader(r io.Reader) io.ReadCloser

参数列表:

r deflate压缩文件的文件标识符

返回

解压后的ReadCloser数据

功能

从r读取deflate压缩数据,返回一个解压过的io.ReadCloser,使用后需要调用关闭该io.ReadCloser

实例

package main

import (
    "bytes"
    "compress/flate"
    "log"
    "fmt"
    "io"
    "os"
)

func main() {
    // 一个缓存区压缩的内容
    buf := bytes.NewBuffer(nil)

    // 创建一个flate.Writer
    flateWrite, err := flate.NewWriter(buf, flate.BestCompression)
    if err != nil {
        log.Fatalln(err)
    }
    defer flateWrite.Close()
    // 写入待压缩内容
    flateWrite.Write([]byte("compress/flate\n"))
    flateWrite.Flush()
    fmt.Printf("压缩后的内容:%s\n", buf)

    // 解压刚压缩的内容
    flateReader := flate.NewReader(buf)
    defer flateWrite.Close()
    // 输出
    fmt.Print("解压后的内容:")
    io.Copy(os.Stdout, flateReader)
}

NewReaderDict

func NewReaderDict(r io.Reader, dict []byte) io.ReadCloser

参数列表

r deflate压缩的数据
dict 解压数据时预设的字典,和NewWriteDict函数里得dict相同

返回

解压后ReadCloser数据

功能

从r读取deflate压缩数据,使用预设得dict字典压缩数据,返回一个压缩过得io.ReadCloser,使用后需要调用者关闭该io.ReadCloser。主要用来读取NewWriteDict压缩的数据

实例

demo:
package main

import (
    "bytes"
    "compress/flate"
    "log"
    "fmt"
    "io"
    "os"
)

func main() {
    // 一个缓冲区存储压缩的内容
    buf := bytes.NewBuffer(nil)

    // 创建一个flate.Write
    flateWrite, err := flate.NewWriterDict(buf, flate.BestCompression, []byte("key"))
    if err != nil {
        log.Fatalln(err)
    }
    defer flateWrite.Close()
    // 写入待压缩内容
    flateWrite.Write([]byte("compress/flate\n"))
    flateWrite.Flush()
    fmt.Println(buf)

    // 解压刚压缩的内容
    flateReader := flate.NewReaderDict(buf, []byte("key"))
    defer flateReader.Close()
    // 输出
    io.Copy(os.Stdout, flateReader)
}

NewWrite

func NewWrite(w io.Write, level int) (*Write, error)

参数列表

1)w 表示输出数据的Write
2)level 表示压缩级别

返回列表

1)*Write 基于压缩级别新生成的压缩数据的Writer
2)error 表示该函数的错误信息

功能

该函数返回一个压缩级别为level的新的压缩用的Writer,压缩级别的范围时1(BestSpeed)to 9(BestCompression)。压缩效果越好的意味着压缩速度越慢。0(NoCompression)表示不做任何压缩;仅仅只需要添加必要的deflate信息,-1(DefaultCompression)表示用默认的压缩级别。如果压缩级别在-1~9的范围内,error返回nil,否则将返回非nil的错误信息。

实例

package main

import (
    "bytes"
    "compress/flate"
    "log"
    "fmt"
)

func main() {
    // 一个缓冲区压缩的内容
    buf := bytes.NewBuffer(nil)

    // 创建一个flate.Writer,压缩级别最好
    flateWrite, err := flate.NewWriter(buf, flate.BestCompression)
    if err != nil {
        log.Fatalln(err)
    }
    defer flateWrite.Close()
    // 写入待压缩内容
    flateWrite.Write([]byte("compress/flate\n"))
    flateWrite.Flush()
    fmt.Println(buf)
}

NewWriteDict

func NewWriteDict(w io.Writer, level int, dict []byte) (*Writer, error)

参数列表

1)w 代表输出数据的Writer
2)level 代表压缩级别
3)dict 代表压缩预设字典

返回列表

1)*Writer 基于压缩级别和预设字典新生成的压缩数据的Writer
2)error 该函数的错误信息

功能

该函数和NewWriter差不多,只不过使用了预设字典进行初始化Writer。使用该Writer压缩的数据只能被使用相同字典初始化的Reader解压。可以实现基于密码的解压缩。

实例

package main

import (
    "bytes"
    "compress/flate"
    "log"
    "fmt"
)

func main() {
    // 一个缓冲区存储压缩的内容
    buf := bytes.NewBuffer(nil)

    // 创建一个flate.Writer,压缩级别最好
    flateWriter, err := flate.NewWriterDict(buf, flate.BestCompression, []byte("key"))
    if err != nil {
        log.Fatalln(err)
    }
    defer flateWriter.Close()
    // 写入待压缩内容
    flateWriter.Write([]byte("compress/flate\n"))
    flateWriter.Flush()
    fmt.Println(buf)
}

InternalError Error

func (e InternalError) Error() string

返回值

表示flate数据自身的错误信息

功能

InternalError其实是一个string,他实现了error接口,用于很方便的返回flate数据自身的错误信息

ReadError Error

func (e *ReadError) Error() string

返回

表示flate读取拷贝数据时的错误信息

功能

ReadError其实是一个struct,他实现了error接口,用于很方便的返回flate读取拷贝数据时的错误信息

WriteError Error

func (e *WriteError) Error() string

返回

表示flate输出数据的错误信息

功能

WriteError是一个struct,他实现了error接口,用于很方便的返回flate输出数据的错误信息

close

func (w *Writer) Close() error

返回

返回一个error,没有错误时返回nil

功能

刷新缓冲并关闭w

Flush

func (w *Writer) Flush() error

返回

返回一个error,没有错误时该error为nil

功能

Flush将缓存中的压缩数据刷新到下层的io.writer中。它主要用在压缩的网络协议中,目的时确保远程读取器有足够的数据重建一个数据包。Flush是阻塞的,直到缓冲中的数据都被写入到下层io.writer中才返回。如果下层io.writer返回一个error,那么Flush也会返回该error。

在zlib库的术语中,Flush等同于Z_SYNC_FLUSH。

Reset

func (w *Writer) Reset(dst io.Writer)

参数列表:

1)dst 重置时将为作w的下层io.Writer

功能

Reset会丢弃现在w的状态,这相当于把dst、w的级别和字典作为参数,重新调用NewWriter或者NewWriterDict函数一样。

Write

func (w *Writer) Write(data []byte) (n int, err error)

参数列表

1)data 代表要写入的字节数据

返回

1)n 写入的字节数
2)err 错误信息,无错误返回nil

功能

Write向w写入数据,最终会将压缩格数的数据写入到w的下层io.Writer中

snappy

google 自家的snappy 压缩优点是非常高的速度和合理的压缩率。压缩率比gzip 小,CPU 占用小。

下面是对几个简单的字符串做snappy 压缩前后对比:

package main

import (
    "fmt"
    "github.com/golang/snappy"
    "io/ioutil"
)

var (
    textMap = map[string]string{
        "a": `1234567890-=qwertyuiop[]\';lkjhgfdsazxcvbnm,./`,
        "b": `1234567890-=qwertyuiop[]\';lkjhgfdsazxcvbnm,./1234567890-=qwertyuiop[]\';lkjhgfdsazxcvbnm,./1234567890-=qwertyuiop[]\';lkjhgfdsazxcvbnm,./1234567890-=qwertyuiop[]\';lkjhgfdsazxcvbnm,./`,
        "c": `浕浉浄浀浂洉洡洣浐洘泚浌洼洽派洿浃浇浈浊测浍济浏浑浒浓浔泿洱涏洀洁洂洃洄洅洆洇洈洊洋洌洎洏洐洑洒洓洔洕洗洠洙洚洛洝洞洟洢洤津洦洧洨洩洪洫洬洭洮洲洳洴洵洶洷洸洹洺活涎`,
        "d": `浕浉浄浀浂洉洡洣浐洘泚浌洼洽派洿浃浇浈浊测浍济浏浑浒浓浔泿洱涏洀洁洂洃洄洅洆洇洈洊洋洌洎洏洐洑洒洓洔洕洗洠洙洚洛洝洞洟洢洤津洦洧洨洩洪洫洬洭洮洲洳洴洵洶洷洸洹洺活涎浕浉浄浀浂洉洡洣浐洘泚浌洼洽派洿浃浇浈浊测浍济浏浑浒浓浔泿洱涏洀洁洂洃洄洅洆洇洈洊洋洌洎洏洐洑洒洓洔洕洗洠洙洚洛洝洞洟洢洤津洦洧洨洩洪洫洬洭洮洲洳洴洵洶洷洸洹洺活涎浕浉浄浀浂洉洡洣浐洘泚浌洼洽派洿浃浇浈浊测浍济浏浑浒浓浔泿洱涏洀洁洂洃洄洅洆洇洈洊洋洌洎洏洐洑洒洓洔洕洗洠洙洚洛洝洞洟洢洤津洦洧洨洩洪洫洬洭洮洲洳洴洵洶洷洸洹洺活涎`,
    }
    imgSrc = []string{
        "1.jpg", "2.jpg", "3.jpg", "4.jpg",
    }
)

func main() {

    for k, v := range textMap {
        got := snappy.Encode(nil, []byte(v))
        fmt.Println("k:", k, "len:", len(v), len(got))
    }

    fmt.Println("snappy jpg")
    for _, v := range imgSrc {
        buf, err := ioutil.ReadFile(v)
        if err == nil {
            got := snappy.Encode(nil, buf)
            fmt.Println("k:", v, "len:", len(buf), len(got))
        }
    }
}

输出:

k: a len: 46 48
k: b len: 184 58
k: c len: 246 250
k: d len: 738 274
snappy jpg
k: 1.jpg len: 302829 282525
k: 2.jpg len: 89109 89051
k: 3.jpg len: 124463 123194
k: 4.jpg len: 420886 368608

如果字符串包含重复字符多压缩才看到效果,对jpg 图片的压缩率不大。

对一个实际使用的数据库是否使用snappy 做对比,用户和文章都是10万,文章内容较简单。

使用snappy 压缩前:

用时 4m32.916312692s
数据库占用空间 176,209,920 字节(磁盘上的 172 MB)
使用snappy 压缩后:

用时 4m6.750271414s
数据库占用空间 159,424,512 字节(磁盘上的 150.9 MB)

从使用时间上看,此例压缩使用的CPU 时间小于数据压缩后省下来的数据存储IO 占用的时间。因为文章数据较短、内容简单,压缩效果不明显。

compress/gzip

golang系统自带的包里边compress/gzip就可以实现在代码中实现gzip的功能。

func Example_writerReader() {
    var buf bytes.Buffer
    zw := gzip.NewWriter(&buf)

    // Setting the Header fields is optional.
    zw.Name = "a-new-hope.txt"
    zw.Comment = "an epic space opera by George Lucas"
    zw.ModTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)

    _, err := zw.Write([]byte("A long time ago in a galaxy far, far away..."))
    if err != nil {
        log.Fatal(err)
    }

    if err := zw.Close(); err != nil {
        log.Fatal(err)
    }

    zr, err := gzip.NewReader(&buf)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Name: %s\nComment: %s\nModTime: %s\n\n", zr.Name, zr.Comment, zr.ModTime.UTC())

    if _, err := io.Copy(os.Stdout, zr); err != nil {
        log.Fatal(err)
    }

    if err := zr.Close(); err != nil {
        log.Fatal(err)
    }

    // Output:
    // Name: a-new-hope.txt
    // Comment: an epic space opera by George Lucas
    // ModTime: 1977-05-25 00:00:00 +0000 UTC
    //
    // A long time ago in a galaxy far, far away...
}

方法说明:

func NewWriter(w io.Writer) *Writer

NewWriter返回一个Writer接口,调用Writer的write方法把数据压缩后写入w。并由调用者负责关闭接口,有另外一个创建Writer的接口func NewWriterLevel(w io.Writer, level int) (*Writer, error) 其使用方法和NewWriter一样,就是增加了一个压缩级别。压缩级别和gzipgzip的压缩级别定义一致。具体可以看每天回顾linux命令(gzip) 中对压缩效率的介绍。

Writer接口方法介绍

1、func (z *Writer) Close() error

关闭Writer接口,但是不关闭创建接口是引入的io.Writer。

2、func (z *Writer) Flush() error

把缓存中的数据刷到初始化时候传入的io.Writer中。注意,这个操作是个阻塞操作。

3、func (z *Writer) Reset(w io.Writer)

刷新Writer的状态为初始状态,并更替其内部的io.Writer

4、func (z *Writer) Write(p []byte) (int, error)

把p的内容压缩后写入接口实例内部的io.Writer中。支持多次写入,后面写入的拼接在先写入的后面。

func NewReader(r io.Reader) (*Reader, error)

NewReader创建一个新的Reader接口,并且将Reader的内容赋值为r,压缩数据存储在r中,并由调用者负责关闭接口。

Reader接口方法介绍

1、func (z *Reader) Close() error

关闭Reader接口,但是不关闭创建接口是引入的io.Reader。

2、func (z *Reader) Multistream(ok bool)

设置Reader接口是否支持多流。

3、func (z *Reader) Read(p []byte) (n int, err error)

将io.Reader内容部分的内容解压后复制到p。

4、func (z *Reader) Reset(r io.Reader) error

刷新Reader的状态为初始状态,并更替其内部的io.Reader

文件压缩实例

定义解压缩文件接口CompressFile和DeCompressFile:

package gziptest

import (
    "compress/gzip"
    "io"
    "os"
)

//压缩文件Src到Dst

func CompressFile(Dst string, Src string) error {
    newfile, err := os.Create(Dst)
    if err != nil {
        return err
    }
    defer newfile.Close()

    file, err := os.Open(Src)
    if err != nil {
        return err
    }

    zw := gzip.NewWriter(newfile)

    filestat, err := file.Stat()
    if err != nil {
        return nil
    }

    zw.Name = filestat.Name()
    zw.ModTime = filestat.ModTime()
    _, err = io.Copy(zw, file)
    if err != nil {
        return nil
    }

    zw.Flush()
    if err := zw.Close(); err != nil {
        return nil
    }
    return nil
}

//解压文件Src到Dst
func DeCompressFile(Dst string, Src string) error {
    file, err := os.Open(Src)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    newfile, err := os.Create(Dst)
    if err != nil {
        panic(err)
    }
    defer newfile.Close()

    zr, err := gzip.NewReader(file)
    if err != nil {
        panic(err)
    }

    filestat, err := file.Stat()
    if err != nil {
        panic(err)
    }

    zr.Name = filestat.Name()
    zr.ModTime = filestat.ModTime()
    _, err = io.Copy(newfile, zr)
    if err != nil {
        panic(err)
    }

    if err := zr.Close(); err != nil {
        panic(err)
    }
    return nil
}

单元测试用例:

gziptest_test.go

package gziptest

import (
    "os"
    "testing"
)

func TestCompressFile(t *testing.T) {
    pwd, _ := os.Getwd()
    newfile, err := os.Create(pwd + "/test.txt")
    if err != nil {
        t.Fatal(err)
    }
    newfile.Write([]byte("hello world!!!!"))
    newfile.Close()

    err = CompressFile(pwd+"/test.gz", pwd+"/test.txt")
    if err != nil {
        t.Fatal(err)
    }
}

func TestDeCompressFile(t *testing.T) {
    pwd, _ := os.Getwd()

    err := DeCompressFile(pwd+"/test2.txt", pwd+"/test.gz")
    if err != nil {
        t.Fatal(err)
    }
}

测试结果:

C:/Go/bin/go.exe test -v [D:/go/src/gziptest]
=== RUN   TestCompressFile
--- PASS: TestCompressFile (0.00s)
=== RUN   TestDeCompressFile
--- PASS: TestDeCompressFile (0.00s)
PASS
ok      gziptest    2.351s

同级目录下增加了三个文件:

其中test.txt和test2.txt内容为:

hello world!!!!
1

test.gz内容为text.txt

zlib

在python的时候就习惯使用zlib进行网页压缩。 golang下同样使用zlib进行压缩解压缩。

Writer

写入func NewWriter

func NewWriter(w io.Writer) *Writer

只能传递 []byte类型数据. NewWriterLevel 可以传递压缩的等级.

package main

import (
    "bytes"
    "compress/zlib"
    "fmt"
    "io"
)

func main() {
    var in bytes.Buffer
    b := []byte(`xiorui.cc`)
    w := zlib.NewWriter(&in)
    w.Write(b)
    w.Close()

    var out bytes.Buffer
    r, _ := zlib.NewReader(&in)
    io.Copy(&out, r)
    fmt.Println(out.String())

}

接口

1.func (*Writer) Write

func (z *Writer) Write(p []byte) (n int, err error)

2.func (*Writer) Close

func (z *Writer) Close() error

Reader

func NewReader(r io.Reader) (io.ReadCloser, error)

设置压缩等级,并压缩数据

func NewWriterLevel(w io.Writer, level int) (io.WriteCloser, os.Error)

func NewWriterLevel(w io.Writer, level int) (io.WriteCloser, os.Error)

下面是几个级别.

const (
        NoCompression = 0
        BestSpeed     = 1

        BestCompression    = 9
        DefaultCompression = -1
)

const (
    NoCompression      = flate.NoCompression
    BestSpeed          = flate.BestSpeed
    BestCompression    = flate.BestCompression
    DefaultCompression = flate.DefaultCompression
)

Goang zlib压缩的效率和性能都还可以, 毕竟rsync也在用这个压缩算法。 其实zlib和gzip对比,貌似用gzip的多一点。 比如nginx的gzip压缩. 以前看过国外一个帖子,是说zlib比gzip更适合那种速度跟压缩效果均衡的场景

lzw

软件包 lzw 实现了 Le Welpel-Ziv-Welch 压缩数据格式,在TA Welch 的“A Technique for High-Performance Data Compression(一种用于高性能数据压缩的技术) 特别是,它实现了 GIF 和 PDF 文件格式所使用的 LZW,这意味着可变宽度代码可达 12 位,前两个非文字代码是一个清晰的代码和一个 EOF 代码。

压缩

func NewWriter(w io.Writer, order Order, litWidth int) io.WriterCloser

参数列表

1)w  输出压缩数据的io.Wrier
2)order lzw数据流的位顺序,有LSB和MSB
3)litWidth  字面码的位数,必须在[2,8]范围内,一般位8

返回值

一个io.WriteCloser,可以将压缩的数据写入其下层的w,调用者使用后要将其关闭

功能说明

NewWriter创建一个新的io.WriterCloser。它将数据压缩后写入w。调用者要在写入结束之后调用返回io.WriterCloser的Close函数关闭。litWidth是字面码的位数,必须在[2,8]范围内,一般为8。

解压

func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser

参数列表:

1)r    待解压的数据
2)order lzw数据流的位顺序,有LSB和MSB
3)litWidth  字面码的位数,必须在[2,8]范围内,一般为8

返回值

一个解压过的io.ReadCloser,调用者使用后要将其关闭

功能说明:

NewReader创建一个新的io.ReadCloser。它从r读取并解压数据。调用者要在读取完之后调用返回io.ReadCloser的Close函数关闭。litWidth是字面码的为数,必须在[2,8]范围内,一般为8.

实例

package main

import (
    "bytes"
    "compress/lzw"
    "fmt"
    "io"
    "os"
)

func main() {
    // 一个缓冲区存储压缩的内容
    buf := bytes.NewBuffer(nil)

    w := lzw.NewWriter(buf, lzw.LSB, 8)
    // 写入待压缩内容
    w.Write([]byte("compress/flate\n"))
    w.Close()
    fmt.Println(buf)

    // 解压
    r := lzw.NewReader(buf, lzw.LSB, 8)
    defer r.Close()
    io.Copy(os.Stdout, r)
}

压缩算法的比较

以下是Google几年前发布的一组测试数据(数据有些老了,有人近期做过测试的话希望能共享出来):

Algorithm   % remaining Encoding    Decoding
GZIP            13.4%   21 MB/s 118 MB/s
LZO             20.5%   135 MB/s    410 MB/s
Zippy/Snappy    22.2%   172 MB/s    409 MB/s

其中:

  • GZIP的压缩率最高,但是其实CPU密集型的,对CPU的消耗比其他算法要多,压缩和解压速度也慢;
  • LZO的压缩率居中,比GZIP要低一些,但是压缩和解压速度明显要比GZIP快很多,其中解压速度快的更多;
  • Zippy/Snappy的压缩率最低,而压缩和解压速度要稍微比LZO要快一些