常见的Content-Encoding的取值
- gzip 具有32位循环冗余检查的LZ77编码,通常由Gzip文件压缩程序产生
- deflate 包含 使用Lempel-Ziv压缩算法(LZ77)和哈夫曼编码的DEFLATE压缩数据流 的ZLIB数据格式
- compress 采用 Lempel-Ziv-Welch (LZW) 压缩算法,这个名称来自 UNIX 系统的 compress 程序
- br 表示采用Brotli算法的编码方式
gzip, compress, 以及deflate编码都是无损压缩算法,用于减少传输报文的大小,不会导致信息损失。其中gzip通常效率最高,使用最为广泛,br是最新的挑战者
为什么要进行数据压缩
- 数据压缩是提高Web站点性能的一种重要手段
- 在不损失有用信息的情况下,通过缩小数据量来减少存储空间,提高其传输、存储和处理效率
- 或通过某种算法对数据进行重组,减少数据的冗余和存储空间。
例如:http压缩对纯文本可以压缩至原内容的40%, 从而节省了60%的数据传输
LZ77算法和Huffman编码
gzip压缩算法、zlib压缩算法等都是对defalte压缩算法的封装,而deflate算法压缩算法底层实现是基于LZ77算法和Huffman编码,先来了解这两种算法的原理。
- 压缩可分成无损压缩和有损压缩。
- gzip、zlib等压缩程序都是无损压缩,因此对于文本的压缩效果比较好,对视频、图片等压缩效果不是很好(视频一般都是采用有损压缩算法),所以对于视频、图片这种已经压缩过的二进制文件可以不需要压缩
1 LZ77算法
含义
LZ77压缩算法采用字典的方式进行压缩,是一个简单但十分高效的数据压缩算法。其方式就是把数据中一些可以组织成短语(最长字符)的字符加入字典,然后再有相同字符出现采用标记来代替字典中的短语,如此通过标记代替多数重复出现的方式以进行压缩。其包含三个关键词:前向缓冲区、滑动窗口以及短语字典
关键词
前向缓冲区
每次读取数据的时候,先把一部分数据预载入前向缓冲区。为移入滑动窗口做准备
滑动窗口
一旦数据通过缓冲区,那么它将移动到滑动窗口中,并变成字典的一部分。
短语字典
从字符序列S1...Sn,组成n个短语。比如字符(A,B,D) ,可以组合的短语为{(A),(A,B),(A,B,D),(B),(B,D),(D)},如果这些字符在滑动窗口里面,就可以记为当前的短语字典,因为滑动窗口不断的向前滑动,所以短语字典也是不断的变化。
算法原理
LZ77的主要算法逻辑就是,先通过前向缓冲区预读数据,然后再向滑动窗口移入(滑动窗口有一定的长度),不断的寻找能与字典中短语匹配的最长短语,然后通过标记符标记。我们还以字符ABD为例子,看如下图:
目前从前向缓冲区中可以和滑动窗口中可以匹配的最长短语就是(A,B),然后向前移动的时候再次遇到(A,B)的时候采用标记符代替。
压缩过程
当压缩数据的时候,前向缓冲区与移动窗口之间在做短语匹配的是后会存在2种情况:
1.找不到匹配时:将未匹配的符号编码成符号标记(多数都是字符本身)
2.找到匹配时:将其最长的匹配编码成短语标记。
3.短语标记包含三部分信息:(滑动窗口中的偏移量(从匹配开始的地方计算)、匹配中的符号个数、匹配结束后的前向缓冲区中的第一个符号)。
一旦把n个符号编码并生成响应的标记,就将这n个符号从滑动窗口的一端移出,并用前向缓冲区中同样数量的符号来代替它们,如此,滑动窗口中始终有最新的短语。
我们采用图例来看:
1、开始
2、滑动窗口中没有数据,所以没有匹配到短语,将字符A标记为A
3、滑动窗口中有A,没有从缓冲区中字符(BABC)中匹配到短语,依然把B标记为B
4、缓冲区字符(ABCB)在滑动窗口的位移6位置找到AB,成功匹配到短语AB,将AB编码为(6,2,C)
5、缓冲区字符(BABA)在滑动窗口位移4的位置匹配到短语BAB,将BAB编码为(4,3,A)
6、缓冲区字符(BCAD)在滑动窗口位移2的位置匹配到短语BC,将BC编码为(2,2,A)
7、缓冲区字符D,在滑动窗口中没有找到匹配短语,标记为D
8、缓冲区中没有数据进入了,结束
解压过程
解压类似于压缩的逆向过程,通过解码标记和保持滑动窗口中的符号来更新解压数据。
当解码字符标记: 将标记编码成字符拷贝到滑动窗口中
解码短语标记: 在滑动窗口中查找响应偏移量,同时找到指定长短的短语进行替换。
我们还是采用图例来看下:
1、开始
2、符号标记A解码
3、符号标记B解码
4、短语标记(6,2,C)解码
5、短语标记(4,3,A)解码
6、短语标记(2,2,A)解码
7、符号标记D解码
优缺点
大多数情况下LZ77压缩算法的压缩比相当高,与选择滑动窗口大小、前向缓冲区大小以及数据熵有关系。其压缩过程是比较耗时的,因为要花费很多时间寻找滑动窗口中的短语匹配,相应解压过程会很快,因为每个标记都明确告知了具体位置。
2 霍夫曼算法
霍夫曼编码是一种基于最小冗余编码的压缩算法。最小冗余编码是指,如果知道一组数据中符号出现的频率,就可以用一种特殊的方式来表示符号从而减少数据需要的存储空间。
- 用较少的位对出现频率高的符号编码
- 用较多的位对出现频率低的符号编码
一个符号不一定必须是文本字符,它可以是任何大小的数据,但往往它只占一个字节。
Huffman Coding:译为哈夫曼编码、赫夫曼编码、霍夫曼编码,是可变字长编码(VLC)的一种。用于无损数据压缩的熵编码(权编码)算法,是一种通过字符出现频率,根据二叉树实现。
构造过程
- 哈夫曼编码会自底向上构造出一棵对应最优编码的二叉树,我们使用下面这个例子来说明哈夫曼树的构造过程
- 首先,我们已知在某个文本中有如下字符及其出现频率:
- 构造过程如下图所示:
-
在一开始,每个字符都已经按照出现频率大小排好顺序
-
在后续的步骤中,每次都将频率最低的两棵树合并,然后用合并后的结果再次排序(注意,排序不是目的,目的是找到这时出现频率最低的两项,以便下次合并。gzip 源码中并没有专门去“排序”,而是使用专门的数据结构把频率最低的两项找到即可)
-
叶子节点用矩形表示,每个叶子节点包含一个字符及其频率。中间节点用圆圈表示,包含其孩子节点的频率之和。中间节点指向左孩子的边标记为 0, 指向右孩子的边标记为 1。一个字符的码字对应从根到其叶节点的路径上的边的标签序列
-
图1为初始集合,有六个节点,每个节点对应一个字符;图2到图5为中间步骤, 图6为最终哈夫曼树。此时每个字符的编码都是前缀码
-
HTTP 的内容编码机制
为了选择要采用的压缩算法,浏览器和服务器之间会使用主动协商机制。浏览器发送 Accept-Encoding
首部,其中包含有它所支持的压缩算法,以及各自的优先级,服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding
首部来告知浏览器它选择了哪一种算法。浏览器获取到响应正文后,根据 Content-Encoding
进行解压。当然,服务器也可以返回未压缩的正文,但这种情况下不允许返回Content-Encoding。