您的位置:首页 > 编程语言 > Go语言

Golang中gzip过滤器的源码分析与解释

2017-11-22 19:46 309 查看

Golang中gzip过滤器的源码分析与解释

与上次相同,这次gzip过滤器的源码,依旧是使用LiteIDE中自带的源码阅读功能来解读的。个人感觉在Go语言的学习中这款软件非常方便

这次我们对gzip包的源码做个整体分析。下面就一部分一部分来看。

引用的包

可看到gzip包引用了如下包:

package gzip

import (
"compress/flate"
"errors"
"fmt"
"hash/crc32"
"io"
"time"
)


结构体与常量的定义

在gzip包的源码中,定义了一个Writer结构体和一些常量:

type Writer struct {
Header
w           io.Writer
level       int
wroteHeader bool
compressor  *flate.Writer
digest      uint32
size        uint32
closed      bool
buf         [10]byte
err         error
}


一个Writer是一个io.WriteCloser,写入到一个被压缩和被写入w的Writer。

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


其中这些常量的定义是从compress/flate包中拷贝来的,所以在使用时,需要引用gzip包的时候就没必要再引用flate包了。

NewWriter函数

func NewWriter(w io.Writer) *Writer {
z, _ := NewWriterLevel(w, DefaultCompression)
return z
}


该函数返回一个新的Writer。要在Writer.Header中设置字段的调用者必须先执行此操作。

Writer可能会被缓冲,直到调用Close才会被刷新。而调用Close需要调用者自己完成。

这是对于Write,Flush或Close的第一次调用。

NewWriterLevel及init函数

NewWriterLevel:

func NewWriterLevel(w io.Writer, level int) (*Writer, error) {
if level < HuffmanOnly || level > BestCompression {
return nil, fmt.Errorf("gzip: invalid compression level: %d", level)
}
z := new(Writer)
z.init(w, level)
return z, nil
}


init:

func (z *Writer) init(w io.Writer, level int) {
compressor := z.compressor
if compressor != nil {
compressor.Reset(w)
}
*z = Writer{
Header: Header{
OS: 255, // unknown
},
w:          w,
level:      level,
compressor: compressor,
}
}


NewWriterLevel函数与NewWriter函数很相似,区别在于,NewWriterLevel函数指定了压缩级别,而不是和NewWriter一样设为DefaultCompression。

这里的压缩级别可以是DefaultCompression,NoCompression,HuffmanOnly

,或者BestSpeed和BestCompression中的任何整数值。

如果级别有效,则error会返回nil。

init就是一个初始化函数,没什么特别的地方,之后来看看其他的函数。

Reset函数

func (z *Writer) Reset(w io.Writer) {
z.init(w, z.level)
}


Reset函数会舍弃掉Writer z的当前状态,并使其等于之前NewWriter或NewWriterLevel给它的原始状态,但是这些都会写入w。

这样做是为了可以重复利用一个Writer,而不是再新分配一个新的w。

writeBytes函数

func (z *Writer) writeBytes(b []byte) error {
if len(b) > 0xffff {
return errors.New("gzip.Write: Extra data is too large")
}
le.PutUint16(z.buf[:2], uint16(len(b)))
_, err := z.w.Write(z.buf[:2])
if err != nil {
return err
}
_, err = z.w.Write(b)
return err
}


writeBytes函数的作用是把一个长度为前缀的byte切片写入到z.w。

writeString函数

func (z *Writer) writeString(s string) (err error) {
needconv := false
for _, v := range s {
if v == 0 || v > 0xff {
return errors.New("gzip.Write: non-Latin-1 header string")
}
if v > 0x7f {
needconv = true
}
}
if needconv {
b := make([]byte, 0, len(s))
for _, v := range s {
b = append(b, byte(v))
}
_, err = z.w.Write(b)
} else {
_, err = io.WriteString(z.w, s)
}
if err != nil {
return err
}
// GZIP strings are NUL-terminated.
z.buf[0] = 0
_, err = z.w.Write(z.buf[:1])
return err
}


writeString函数的作用是将一个UTF-8字符串以GZIP的格式写入z.w。

GZIP格式: GZIP(RFC 1952)指定字符串是以NUL结尾的ISO 8859-1(Latin-1)。

如果是非Latin-1格式则返回error;如果是非ASCII格式,则会发生转换。

Write函数

func (z *Writer) Write(p []byte) (int, error) {
if z.err != nil {
return 0, z.err
}
var n int
if !z.wroteHeader {
z.wroteHeader = true
z.buf = [10]byte{0: gzipID1, 1: gzipID2, 2: gzipDeflate}
if z.Extra != nil {
z.buf[3] |= 0x04
}
if z.Name != "" {
z.buf[3] |= 0x08
}
if z.Comment != "" {
z.buf[3] |= 0x10
}
if z.ModTime.After(time.Unix(0, 0)) {
le.PutUint32(z.buf[4:8], uint32(z.ModTime.Unix()))
}
if z.level == BestCompression {
z.buf[8] = 2
} else if z.level == BestSpeed {
z.buf[8] = 4
}
z.buf[9] = z.OS
n, z.err = z.w.Write(z.buf[:10])
if z.err != nil {
return n, z.err
}
if z.Extra != nil {
z.err = z.writeBytes(z.Extra)
if z.err != nil {
return n, z.err
}
}
if z.Name != "" {
z.err = z.writeString(z.Name)
if z.err != nil {
return n, z.err
}
}
if z.Comment != "" {
z.err = z.writeString(z.Comment)
if z.err != nil {
return n, z.err
}
}
if z.compressor == nil {
z.compressor, _ = flate.NewWriter(z.w, z.level)
}
}
z.size += uint32(len(p))
z.digest = crc32.Update(z.digest, crc32.IEEETable, p)
n, z.err = z.compressor.Write(p)
return n, z.err
}


Write函数把一个压缩形式的p写入到到底层io.Writer。在Writer函数结束之前,压缩的字节不一定会被刷新。

函数中,关于ModTime,MTIME取0意味着没有设置修改的时间。

Flush函数

func (z *Writer) Flush() error {
if z.err != nil {
return z.err
}
if z.closed {
return nil
}
if !z.wroteHeader {
z.Write(nil)
if z.err != nil {
return z.err
}
}
z.err = z.compressor.Flush()
return z.err
}


Flush函数刷新所有正在挂起的被压缩的数据,去到底层writer。

Flush函数主要用于压缩网络协议,以确保远程读取器有足够的数据来重建数据包。 在数据写入之前,刷新不会返回。 如果底层写入程序返回错误,则Flush返回该错误。

Close函数

func (z *Writer) Close() error {
if z.err != nil {
return z.err
}
if z.closed {
return nil
}
z.closed = true
if !z.wroteHeader {
z.Write(nil)
if z.err != nil {
return z.err
}
}
z.err = z.compressor.Close()
if z.err != nil {
return z.err
}
le.PutUint32(z.buf[:4], z.digest)
le.PutUint32(z.buf[4:8], z.size)
_, z.err = z.w.Write(z.buf[:8])
return z.err
}


Close函数通过将所有未写入的数据刷新到底层的io.Writer,并写入GZIP页脚来关闭Writer。

需要注意的是,Close函数不会关闭底层的io.Writer。

总结

以上就是gzip包的所有源码,可以看出gzip对于一个writer的操作有Write、Close、Flush三种,功能都很明确且很方便,同时也有writeBytes和writeString这样的函数,使得操作简化。使用gzip包进行压缩方面的操作可以很容易完成。要注意在使用writeString的时候明确字符串格式等问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: