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

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的时候明确字符串格式等问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值