数据压缩原理
-
-
- 压缩原理
- 压缩极限
- Deflate 压缩算法
- 信息熵
- LZ77 算法原理
-
- LZ77 压缩
- LZ77 解压
- 优缺点
- Huffman 算法原理
-
- 前缀码
- 哈夫曼编码
- 哈夫曼压缩和解压文件(编程实现)
- deflate 采用的改进版 LZ77 算法
- deflate 无损压缩解压算法(先 LZ77 压缩,然后 huaffman 编码)
- gzip 格式分析
- zlib 库 API 分析
-
- 常用的函数
- 压缩函数介绍
-
- deflateInit2 : 初始化函数
- deflate:压缩函数
- deflateEnd:资源释放
- 解压函数介绍
-
- inflateInit2:解压初始化
- inflate:解压函数
- inflateEnd:资源释放
- nginx 优化之 gzip 压缩提升网站速度
-
压缩原理
压缩原理其实很简单,就是找出那些重复出现的字符串,然后用更短的符号代替,从而达到缩短字符串的目的。比如,有一篇文章大量使用"中华人民共和国"这个词语,我们用"中国"代替,就缩短了 5 个字符,如果用"华"代替,就缩短了 6 个字符。事实上,只要保证对应关系,可以用任意字符代替那些重复出现的字符串。本质上,所谓"压缩"就是找出文件内容的概率分布,将那些出现概率高的部分代替成 更 短 的 形 式 。 所 以 , 内 容 越 是 重 复 的 文 件 , 就 可 以 压 缩 地 越 小 。 比 如 ,“ABABABABABABAB"可以压缩成"7AB”。
相应地,如果内容毫无重复,就很难压缩。极端情况就是,遇到那些均匀分布的随机字符串,往往连一个字符都压缩不了。比如,任意排列的 10 个阿拉伯数字(5271839406),就是无法压缩的;再比如,无理数(比如 π)也很难压缩。
压缩极限
香农极限
∑ log2(1/pn) / n = log2(1/p1)/n + log2(1/p2)/n + … + log2(1/pn)/n
(1)下面是一个例子。假定有两个文件都包含 1024 个符号,在 ASCII 码的情况下,它们的长度是相等的,都是 1KB。甲文件的内容 50%是 a,30%b,20%是 c,文本里面只有abc,则平均每个符号要占用 1.49 个二进制位。
0.5 * log2(1/0.5) + 0.3 * log2(1/0.3) + 0.2 * log2(1/0.2) = 1.49
(2)比如每个字节的数值概率是 0~255,均匀分布每个数值出现的概率 1/256,如果一段文字的字节数值是平均分布,则 pn = 1/256,计算出极限为 8。
256 个数值没有重复
Log21/(1/256) = Log2256 = 8
8/256 + 8/256 + 8/256 …… 8/256 = 8bit
关注 字节的数值
注:log2 计算网址:http://tool.91maths.com/log/15.html
Deflate 压缩算法
deflate 是 zip 压缩文件的默认算法。其实 deflate 现在不光用在 zip 文件中,在 7z,xz 等其他的压缩文件中都用。实际上 deflate 只是一种压缩数据流的算法。 任何需要流式压缩的地方都可以用。
deflate 算法下的压缩器有三种压缩模型:
- 不压缩数据,对于已经压缩过的数据,这是一个明智的选择。 这样的数据会会稍稍增加,但是会小于在其上再应用一种压缩算法。
- 压缩,先用 LZ77,然后用 huffman 编码。 在这个模型中压缩的树是 Deflate 规范规定定义的, 所以不需要额外的空间来存储这个树。
- 压缩,先用 LZ77,然后用 huffman 编码。 压缩树是由压缩器生成的,并与数据一起存储。
数据被分割成不同的块,每个块使用单一的压缩模式。 如果压缩器要在这三种压缩模式中相互切换,必须先结束当前的块,重新开始一个新的块。
信息熵
数据为何是可以压缩的,因为数据都会表现出一定的特性,称为熵。绝大多数的数据所表现出来的容量往往大于其熵所建议的最佳容量。比如所有的数据都会有一定的冗余性,我们可以把冗余的数据采用更少的位对频繁出现的字符进行标记,也可以基于数据的一些特性基于字典编码,代替重复多余的短语。
LZ77 算法原理
Ziv 和 Lempel 于 1977 年发表题为“顺序数据压缩的一个通用算法(A Universal Algorithm for Sequential Data Compression )。LZ77 压缩算法采用字典的方式进行压缩,是一个简单但十分高效的数据压缩算法。其方式就是把数据中一些可以组织成短语(最长字符)的字符加入字典,然后再有相同字符出现采用标记来代替字典中的短语,如此通过标记代替多数重复出现的方式以进行压缩。要理解这种算法,需先了解 3 个关键词:短语字典,滑动窗口和向前缓冲区。
关键词术语
- 前向缓冲区 :每次读取数据的时候,先把一部分数据预载入前向缓冲区。为移入滑动窗口做准备
- 滑动窗口:一旦数据通过缓冲区,那么它将移动到滑动窗口中,并变成字典的一部分。滑动窗口需要预设一个定值。
- 短语字典:
从字符序列 S1 … Sn,组成 n 个短语。比如字符(A,B,D) ,可以组合的短语为{(A),(A,B),(A,B,D),(B),(B,D),(D)},如果这些字符在滑动窗口里面,就可以记为当前的短语字典,因为滑动窗口不断的向前滑动,所以短语字典也是不断的变化。
LZ77 的主要算法逻辑就是,先通过前向缓冲区预读数据,然后再向滑动窗口移入(滑动窗口有一定的长度),不断的寻找能与字典中短语匹配的最长短语,然后通过标记符标记。我们还以字符 ABD 为例子,看如下图:
目前从前向缓冲区中可以和滑动窗口中可以匹配的最长短语就是(A,B),然后向前移动的时候再次遇到(A,B)的时候采用标记符代替。
LZ77 压缩
当压缩数据的时候,前向缓冲区与移动窗口之间在做短语匹配的是后会存在 2 种情况:
(1)找不到匹配时:将未匹配的符号编码成符号标记(多数都是字符本身)
(2)找到匹配时:将其最长的匹配编码成短语标记。
短语标记包含三部分信息:
(1)滑动窗口中的偏移量(从匹配开始的地方计算);
(2)匹配中的符号个数;
(3)匹配结束后的前向缓冲区中的第一个符号)。
一旦把 n 个符号编码并生成相应的标记,就将这 n 个符号从滑动窗口的一端移出,并用前向缓冲区中同样数量的符号来代替它们,如此,滑动窗口中始终有最新的短语。
下面图例是用 LZ77 算法压缩字符串的过程,其中滑动窗口大小为 8 个字节(索引位置从 0 开始),前向缓冲区大小为 4 个字节(在实际中,滑动窗口典型的大小为 4KB 字 节,前向缓冲区大小通常小于 100 字节):
1、开始
2、滑动窗口中没有数据,所以没有匹配到短语,将字符 A 标记为 A
3、滑动窗口中有 A,没有从缓冲区中字符(BABC)中匹配到短语,依然把 B 标记为B
4、缓冲区字符(ABCB)在滑动窗口的位移 6 位置找到 AB,成功匹配到短语 AB,将AB 编码为(6,2,C),之所以是 6,是因为窗口的 A 在滑动窗口的索引[6]位置。
5、缓冲区字符(BABA)在滑动窗口位移 4 的位置匹配到短语 BAB,将 BAB 编码为(4,3,A),之所以偏移为 4 是因为
6、缓冲区字符(BCAD)在滑动窗口位移 2 的位置匹配到短语 BC,将 BC 编码为(2,2,A)
7、缓冲区字符 D,在滑动窗口中没有找到匹配短语,标记为 D
8、缓冲区中没有数据进入了,结束
LZ77 解压
解压类似于压缩的逆向过程,通过解码标记和保持滑动窗口中的符号来更新解压数据。
当解码字符标记:将标记编码成字符拷贝到滑动窗口中
解码短语标记:在滑动窗口中查找相应偏移量,同时找到指定长短的短语进行替换。
我们还是采用图例来看下:
1、开始
2、符号标记 A 解码
3、符号标记 B 解码
4、短语标记(6,2,C)解码
是根据 3 中的索引[6]开始,得到 AB,就是重复 AB 再加入上 C,就成了 ABABC,并且滑动窗口滑到最右边的位置。
5、短语标记(4,3,A)解码
6、短语标记(2,2,A)解码
7、符号标记 D 解码
优缺点
大多数情况下 LZ77 压缩算法的压缩比相当高,当然了也和你选择滑动窗口大小,以及前向缓冲区大小,以及数据熵有关系。其压缩过程是比较耗时的,因为要花费很多时间寻找滑动窗口中的短语匹配,不过解压过程会很快,因为每个标记都明确告知在哪个位置可以读取了。
使用固定大小窗口进行短语匹配,而不是在所有已经编码的信息中匹配,是因为匹配算法的时间消耗往往很多,必须限制字典的大小才能保证算法的效率;随着压缩的进程滑动字典窗口,使其中总包含最近编码过的信息,是因为对大多数信息而言,要编码的字符串往往在最近的上下文中更容易找到匹配串。 常见的窗口固定大小 4k、8k 、 16k。
Huffman 算法原理
前缀码
在一个字符集中,任何一个字符的编码都不是另一个字符编码的前缀,即前缀码。例如,有两个码字 111 与 1111,那么这两个码字就不符合前缀码的规则,因为 111 是1111 的前缀。放到二叉树里来讲,只用叶子节点编码的码字才是前缀码,如果同时使用中间节点和叶子节点编码,那结果就不是前缀码。因为压缩中经过编码的码字全部是前缀码,所以在对照码表解压的时候,碰到哪个码字就是哪个码字,不用担心出现某个字符的编码是另一个字符的编码的前缀的情况,该意识一定要具备。
关于前缀码,下面一段话摘自《算法导论》:“前缀码的作用是简化解码过程。由于没有码字是其他码字的前缀,编码文件的开始码字是无歧义的。我们可以简单的识别出开始码字,将其转换会原字符,然后对编码文件剩余部分重复这种解码过程。
哈夫曼编码
哈夫曼设计了一个贪心算法来构造最优前缀码,被称为哈夫曼编码(Huffman code),其正确性证明依赖于贪心选择性质和最优子结构。哈夫曼编码可以很有效的压缩数据,具体压缩率依赖于数据本身的特性。这里我们先介绍几个概念:码字、码字长度、定长编码与变长编码。
每个字符可以用一个唯一的二进制串表示,这个二进制串称为这个字符的码字,这个二进制串的长度称为这个码字的码字长度。码字长度固定就是定长编码,码字长度不同则为变长编码。变长编码可以达到比定长编码好得多的压缩率,其思想是赋予高频字符(出现频率高的字符)短(码字长度较短)码字,赋予低频字符长码字。例如,我们用 ASCII 字符编辑一个文本文档,不论字符在整个文档中出现的频率,每个字符都要占用一个字节;如果我们使用变长编码的方式,每个字符因在整个文档中的出现频率不同导致码字长度不同,有的可能占用一个字节,而有的可能只占用一比特,这个时候,整个文档占用空间就会比较小了。当然,如果这个文本文档相当大,导致每个字符的出现频率基本相同,那么此时所谓变长编码在压缩方面的优势就基本不存在了(这点要十分明确,这是为什么压缩要分块的原因之一,后续源码分析会详细讲解)。
哈夫曼编码会自底向上构造出一棵对应最优编码的二叉树,我们使用下面这个例子来说明哈夫曼树的构造过程。首先,我们已知在某个文本中有如下字符及其出现频率,
构造过程如下图所示:
图 1 到图 6 列除了整个哈夫曼树构造过程中的每个步骤。在一开始,每个字符都已经按照出现频率大小排好顺序,在后续的步骤中,每次都将频率最低的两棵树合并,然后用合并后的结果再次排序(注意,排序不是目的,目的是找到这时出现频率最低的两项,以便下次合并。gzip 源码中并没有专门去“排序”,而是使用专门的数据结构把频率最低的两项找到即可)。叶子节点用矩形表示,每个叶子节点包含一个字符及其频率。中间节点用圆圈表示,包含其孩子节点的频率之和。中间节点指向左孩子的边标记为 0,指向右孩子的边标记为 1。一个字符的码字对应从根到其叶节点的路径上的边的标签序列。图 1 为初始集合,有六个节点,每个节点对应一个字符;图 2 到图 5 为中间步骤,图 6 为最终哈夫曼树。此时每个字符的编码都是前缀码。
哈夫曼压缩和解压文件(编程实现)
利用库中的优先级队列实现哈夫曼树,最后基于哈夫曼树最终实现文件压缩。
- 统计文件中字符出现的次数,利用优先级队列构建 Haffman 树,生成 Huffman 编码。构造过程可以使用 priority_queue 辅助,每次 pq.top()都可以取出权值(频数)最小的节点。每取出两个最小权值的节点,就 new 出一个新的节点,左右孩子分别指向它们。然后把这个新节点 push 进优先队列。
- 压缩:利用 Haffman 编码对文件进行压缩,即在压缩文件中按顺序存入每个字符的 Haffman 编码。 码表(实际存储是对应数值的概率,然后调用程序生成码表) + 编码
- 将文件中出现的字符以及它们出现的次数写入配置文件中,以便后续压缩使用。
- 解压缩:利用配置文件重构 Haffman 树,对文件进行解压缩。
deflate 采用的改进版 LZ77 算法
三个字节以上的重复串才进行编码,否则不进行编码:
现在来说明一下,为什么最小匹配为 3 个字节。这是由于,gzip 中,<匹配长度,到匹配串开头的距离>对中,"匹配长度"的范围为 3-258,也就是 256 种可能值,需要 8bit来保存。"到匹配串开头的距离"的范围为 0-32K,需要 15bit 来保存。所以一个<匹配长度,到匹配串开头的距离>对需要 23 位,差一位 3 个字节。如果匹配串小于 3 个字节的话,使用<匹配长度,到匹配串开头的距离>对进行替换,不但没有压缩,反而还会增大。所以保存<匹配长度,到匹配串开头的距离>对所需要的位数,决定了最小匹配长度至少要为 3 个字节。
deflate 无损压缩解压算法(先 LZ77 压缩,然后 huaffman 编码)
一旦原始数据被转换成了字符和长度距离对组成的串,这些数据必须由 huffman编码表示。
deflate 中的 huffman 编码:
对 LZ77 得到的压缩后结果,需要统计字符生成编码表 huffmantree(指示每个编码代表什么字符),根据码表对内容进行编码,具体的压缩大小在于精细分配结构体的位域来实现 Huffman 编码的压缩效果的。
编码表 huffmantree 和编码后的 data 都一起放置在文件中。
deflate 中的解压:
读取二进制文件,构建 huffmantree 表,读取数据根据 huffmantree 生成字符(这些字符是符合 LZ77 算法的)。
用 LZ77 解码,这个时候应该需要对窗口生成哈希表(数组+链表);对解压的数据,进行搜索匹配拷贝替换为相应的串即可。
gzip 格式分析
gzip 的压缩原理是:先使用 LZ77 算法的一个变种进行压缩,对得到的结果再使用静态或动态哈夫曼编码的方法进行压缩;bzip2 的压缩原理为:使用了一个游程编码器进行编码,接下来块排序压缩和 Move-to-Front(MTF ) 变换进一步产生大量相同符号,进一步使用另一个游程编码器进行编码。最后结果用 Huffman 编码,将一个消息头与其打包;LZMA 是 Deflate 和 LZ77 算法改良和优化后的压缩算法,而 Deflate 则是同时使用了 LZ77 算法与哈夫曼编码的一个无损数据压缩算法。
deflate(RFC1951):一种压缩算法,使用 LZ77 和哈夫曼进行编码;
zlib(RFC1950):一种格式,是对 deflate 进行了简单的封装,他也是一个实现库(delphi
中有 zlib,zlibex);
gzip(RFC1952):一种格式,也是对 deflate 进行的封装;
https://www.rfc-editor.org/rfc/rfc1952.txt
gzip = gzip 头 + deflate 编码的实际内容 + gzip 尾
zlib = zlib 头 + deflate 编码的实际内容 + zlib 尾
GZIP 本身只是一种文件格式,其内部通常采用 DEFLATE 数据格式,而 DEFLATE 采用 LZ77 压缩算法来压缩数据。
GZIP 文件由 1 到多个“块”组成,实际上通常只有 1 块。每个块包含头、数据和尾三
部分。块的概貌如下:
头部分
ID1 与 ID2:各 1 字节。固定值,ID1 = 31 (0x1F),ID2 = 139(0x8B),指示 GZIP 格式。CM:1 字节。压缩方法。目前只有一种:CM = 8,指示 DEFLATE 方法。FLG:1 字节。标志。
bit 0 FTEXT - 指示文本数据
bit 1 FHCRC - 指示存在 CRC16 头校验字段
bit 2 FEXTRA - 指示存在可选项字段
bit 3 FNAME - 指示存在原文件名字段
bit 4 FCOMMENT - 指示存在注释字段
bit 5-7 保留
MTIME:4 字节。更改时间。UINX 格式。XFL:1 字节。附加的标志。当 CM = 8 时,XFL = 2 - 最大压缩但最慢的算法;XFL = 4 - 最快但最小压缩的算法 OS:1 字节。操作系统,确切地说应该是文件系统。有下列定义:
0 - FAT 文件系统 (MS-DOS, OS/2, NT/Win32)
1 - Amiga
2 - VMS/OpenVMS
3 - Unix
4 - VM/CMS
5 - Atari TOS
6 - HPFS 文件系统 (OS/2, NT)
7 - Macintosh
8 - Z-System
9 - CP/M
10 - TOPS-20
11 - NTFS 文件系统 (NT)
12 - QDOS
13 - Acorn RISCOS
255 - 未知
额外的头字段:
(若 FLG.FEXTRA = 1)
+---+---+---+---+===============//================+
|SI1|SI2| XLEN | 长度为 XLEN 字节的可选项 |
+---+---+---+---+===============//================+(若 FLG.FNAME = 1)
+=======================//========================+
| 原文件名(以 NULL 结尾) |
+=======================//========================+(若 FLG.FCOMMENT = 1)
+=======================//========================+
| 注释文字(只能使用 iso-8859-1 字符,以 NULL 结尾) |
+=======================//========================+(若 FLG.FHCRC = 1)
+---+---+
| CRC16 |
+---+---+
存在额外的可选项时,SI1 与 SI2 指示可选项 ID,XLEN 指示可选项字节数。如 SI1
= 0x41 (‘A’),SI2 = 0x70 (‘P’),表示可选项是 Apollo 文件格式的额外数据。
数据部分
DEFLATE 数据格式,包含一系列子数据块。子块概貌如下:
+......+......+......+=============//============+
|BFINAL| BTYPE | 数据 |
+......+......+......+=============//============+
BFINAL:1 比特。0 - 还有后续子块;1 - 该子块是最后一块。BTYPE:2 比特。00 - 不压缩;01 - 静态 Huffman 编码压缩;10 - 动态 Huffman 编码压缩;11 - 保留。
尾部分
CRC32:4 字节。原始(未压缩)数据的 32 位校验和。ISIZE:4 字节。原始(未压缩)数据的长度的低 32 位。
GZIP 中字节排列顺序是 LSB 方式,即 Little-Endian,与 ZLIB 中的相反。
zlib 库 API 分析
1.下载源码包
下载 http://www.zlib.net/,选择 zlib-1.2.11.tar.gz
(1)下载
wget http://www.zlib.net/zlib-1.2.11.tar.gz
(2)解压
tar -zxvf zlib-1.2.11.tar.gz
(3)进入目录
cd zlib-1.2.11
2.编译
(1)配置
./configure
(2)编译
make
(3)检查,要全部为 yes
make check
(4)安装
sudo make install
基础数据结构
常用的函数
压缩函数
deflateInit : 参数比较少,里面的实现其实是调用的 deflateInit2
deflateInit2: 压缩初始化的基础函数,有很多参数,下面会重点介绍。
deflate : 压缩函数。
deflateEnd : 压缩完成以后,释放空间,但是注意,仅仅是释放 deflateInit 中申请的空
间,自己申请的空间还是需要自己释放。
compress : 全部附加选项默认压缩,内部调用 compress2。
compress2 : 带 level 的压缩方式。
解压函数
inflateInit : 解压初始化函数,内部调用的 inflateInit2。
inflateInit2 : 解压初始化的基础函数,后面重点介绍。
infalte : 解压函数。
inflateEnd : 同 deflateEnd 作用类似。
uncompress : 解压缩。
压缩函数介绍
deflateInit2 : 初始化函数
函数原型为 :
int ZEXPORT deflateInit2 ((z_streamp strm, int level,int method, int windowBits, int memLevel, int strategy));
为压缩初始化内部流状态,zalloc,zfree 和 opaque 字段必须在调用之前初始化,如果 zalloc 和 zfree 被初始化为 Z_NULL,deflateInit 会更新它们而使用默认的分配函数。
压缩级别必须为 Z_DEFAULT_COMPRESSION,或者 0 到 9 之间的数;1 表示最快速度的压缩,9 表示最优压缩,0 不做任何压缩,Z_DEFAULT_COMPRESSION 是速度和最优压缩的折衷(一般为 6)
函数成功返回 Z_OK,如果没有足够的内存则返回 Z_MEM_ERROR,如果不是一个有效的压缩级别则返回 Z_STREAM_ERROR,版本不兼容则返回 Z_VERSION_ERROR。如果没有错误信息则 msg 被设置为 0。
z_stream:这个是压缩上下文,我们依照官方给的例子进行初始化
strm.zalloc = NULL;
strm.zfree = NULL;
strm.opaque = NULL;
strm.next_in = 你的待压缩数据
strm.next_out = 压缩以后数据存储的 buffer
strm.avail_in = 待压缩数据的长度
strm.avail_out = 压缩数据存储 buffer 的长度.
level: 压缩的等级,目前有四个值
#define Z_NO_COMPRESSION 0 //不压缩
#define Z_BEST_SPEED 1 //速度优先,可以理解为最低限度的压缩.
#define Z_BEST_COMPRESSION 9 //压缩优先,但是速度会有些慢
#define Z_DEFAULT_COMPRESSION (-1) //默认选项,compress 里面用的就是这个选项
method:值只有一个,当前唯一的 defalte 压缩方法,用于以后扩展
#define Z_DEFLATED 8
/* The deflate compression method (the only one supported in this version) */
windowBits: 窗口比特数
-(15 ~ 8) : 纯 deflate 压缩
+(15 ~ 8) : 带 zlib 头和尾
> 16 : 带 gzip 头和尾
memLevel: 目前只有一个选项,MAX_MEM_LEVEL,无非是运行过程中对内存使用的限制
/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
# ifdef MAXSEG_64K
# define MAX_MEM_LEVEL 8
# else
# define MAX_MEM_LEVEL 9
# endif
#endif
strategy:用于调整压缩算法,直接给默认就行 Z_DEFAULT_STRATEGY.
#define Z_FILTERED 1 //用于由 filter(或者称为 predictor)生成的数据
#define Z_HUFFMAN_ONLY 2 //用于强制哈夫曼编码(不做字符匹配)
#define Z_RLE 3 //限制匹配长度为 1
#define Z_FIXED 4 //阻止使用动态哈夫曼编码,从而允许获得更简单的解码
#define Z_DEFAULT_STRATEGY 0 //用于普通数据
/* compression strategy; see deflateInit2() below for details */
Z_FILTERED,用于由 filter(或者称为 predictor)生成的数据.过滤的数据包含很多小的
随机数据。这种情况下,压缩算法能够获得更好的压缩效果。该选项可以强制更多的哈
夫 曼 编 码 和 更 少 的 字 符 匹 配 。 有 时 候 可 以 作 为 Z_DEFAULT_STRATEGY 和
Z_HUFFMAN_ONLY 的折衷。
Z_FIXED,阻止使用动态哈夫曼编码,从而允许获得更简单的解码。
strategy 参数只影响压缩比,而不会影响到压缩输出的正确性,因此没有正确的设置也
不要紧。
deflate:压缩函数
函数原型为:
ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
deflate 函数尽可能的压缩数据,当输入缓冲为空或者输出缓冲满了的时候会停止。它会带来输出延迟(读输入数据而没有输出)除非强行刷新缓冲区。
详细的语意如下,deflate 会执行下面的一个或者两个动作都执行:
? 从 next_in 开始压缩输入数据从而更新 next_in 和 avail_in。如果不是所有输入数据可以被处理(因为输出缓冲没有足够的空间),next_in 和 avail_in 会更新,当再次调用 deflate()函数时输入数据会从这一点开始被处理。
? 从 next_out 开始提供更多输出数据从而更新 next_out 和 avail_out,如果 flush参数不是为 0 的化这个动作是强制性的,经常性的强制刷新缓冲区会降低压缩比率,所以只有必要的时候才会设置这个参数(在交际程序时),一些数据也会输出即使刷新没有被设置。
在调用 deflate()函数之前,应用程序必须保证至少上面的一个动作被执行(avail_in或 avail_out 被设置),用提供更多输入数据和/或消耗更多输出数据的方式,从而更新avail_in 或 avail_out;avail_out 在函数调用之前千万不能为 0。应用程序可以随时消耗被压缩的输出数据,举个例子:当输出缓冲满了或者在每次调用 deflate()之后,如果 deflate返回 Z_OK 并 avail_out 为 0 时,deflate()必须再次被调用(说明输出缓存区还有数据应该被读取)
Int flush 的参数:
Z_NO_FLUSH:通常设置为该值,允许压缩算法决定累积多少数据再产生输出,以达到压缩效率最高。
Z_SYNC_FLUSH:将所有等待输出的数据刷新到输出缓冲区,以字节为边界进行对齐。该刷新可能会降低压缩算法的压缩效率,它只用于必要的时候。This completes the current deflate block and follows it with an empty stored block that is three bits plus filler bits to the next byte, followed by four bytes (00 00 ff ff).
Z_FINISH:如果输入和待输出的数据都被处理完,则返回 Z_STREAM_END。如果返回 Z_OK or Z_BUF_ERROR 则需要再次调用 Z_FINISH 直到返回 Z_STREAM_END。
deflateEnd:资源释放
函数原型:
int ZEXPORT deflateEnd OF((z_streamp strm));
压缩完成以后,释放空间,但是注意,仅仅是释放 deflateInit 中申请的空间,自己申请的空间还是需要自己释放。
解压函数介绍
inflateInit2:解压初始化
函数原型:
int ZEXPORT inflateInit2((z_streamp strm, int windowBits);
strm: 和 deflate 一样,初始化三个回调以后即可,有的参考文档说还需要初始化第四个选项,具体记不清哪个了,不过我试过以后发现貌似不用。
windownBits : 含义和 deflateInit2 一样,而且一定要对应起来。
inflate:解压函数
函数原型:
int ZEXPORT inflate OF((z_streamp strm, int flush))
z_streamp : 四个参数.
strm.next_in = 你的待解压数据
strm.next_out = 解压以后数据存储的 buffer
strm.avail_in = 待解压数据的长度
strm.avail_out = 解压数据存储 buffer 的长度.
flush : 和 deflate 一样,如果是 Z_NO_FLUSH 说明还有数据没有解压,如果是 Z_FINISH 说明这是最后一包待解压数据.
inflateEnd:资源释放
函数原型:
int ZEXPORT inflateEnd OF((z_streamp strm));
释放上面两步骤里面申请的资源。
nginx 优化之 gzip 压缩提升网站速度
注:图片、视频、音频等二进制文件没有必要进行压缩。
配置
# 开启 gzip
gzip on;# 启用 gzip 压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用 CPU 时间,后面会有详细说明
gzip_comp_level 1;# 进行压缩的文件类型。javascript 有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css
application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png
application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;# 是否在 http header 中添加 Vary: Accept-Encoding,建议开启
gzip_vary on;# 禁用 IE 6 gzip
gzip_disable "MSIE [1-6]\.";# 设置压缩所需要的缓冲区大小
gzip_buffers 32 4k;# 设置 gzip 压缩针对的 HTTP 协议版本,没做负载的可以不用
# gzip_http_version 1.0;# 开启缓存
location ~* ^.+\.(ico|gif|jpg|jpeg|png)$ {
access_log off; expires 2d;
}location ~* ^.+\.(css|js|txt|xml|swf|wav)$ {
access_log off;expires 24h;
}location ~* ^.+\.(html|htm)$ {
expires 1h;
}location ~* ^.+\.(eot|ttf|otf|woff|svg)$ {
access_log off;expires max;
}# 格式
# expires 30s;
# expires 30m;
# expires 2h;
# expires 30d;
说明
gzip on:打开或关闭 gzip。
Syntax: gzip on | off;
Default: gzip off;
Context: http, server, location, if in location
gzip_buffers:设置用于处理请求压缩的缓冲区数量和大小。比如 32 4K 表示按照内存页(one memory page)大小以 4K 为单位(即一个系统中内存页为 K),申请 32 倍的内存空间。建议此项不设置,使用默认值。
Syntax: gzip_buffers number size;
Default: gzip_buffers 32 4k|16 8k;
Context: http, server, location
gzip_comp_level:设置 gzip 压缩级别,级别越低压缩速度越快文件压缩比越小,反之速度越慢文件压缩比越大。
Syntax: gzip_comp_level level;
Default: gzip_comp_level 1;
Context: http, server, location
gzip_disable:通过表达式,表明哪些 UA 头不使用 gzip 压缩。
Syntax: gzip_disable regex ...;
Default: —
Context: http, server, location
This directive appeared in version 0.6.23
gzip_min_length:正整数,单位为字节,也可用 k 表示千字节,比如写成 1024 与 1k 都可以,效果是一样的,表示当资源大于 1k 时才进行压缩,资源大小取响应头中的 Content-Length 进行比较,经测试如果响应头不存在 Content_length 信息,该限制参数对于这个响应包是不起作用的;另外此处参数值不建议设的太小,因为设的太小,一些本来很小的文件经过压缩后反而变大了,官网没有给出建议值,在此建议 1k 起,因为小于 1k 的也没必要压缩,并根据实际情况来调整设定。
Syntax: gzip_min_length length;
Default: gzip_min_length 20;
Context: http, server, location
gzip_http_version:用于识别 http 协议的版本,早期的浏览器不支持 gzip 压缩,用户会看到乱码,所以为了支持前期版本加了此选项。默认在 http/1.0 的协议下不开启 gzip 压缩。
Syntax: gzip_http_version 1.0 | 1.1;
Default: gzip_http_version 1.1;
Context: http, server, location
在应用服务器前,如果还有一层 Nginx 的集群作为负载均衡,在这一层上,如果没有开启 gzip。
如果我们使用了 proxy_pass 进行反向代理,那么 nginx 和后端的 upstream server 之间默认是用 HTTP/1.0 协议通信的。
如果我们的 Cache Server 也是 nginx,而前端的 nginx 没有开启 gzip。
同时,我们后端的 nginx 上没有设置 gzip_http_version 为 1.0,那么 Cache 的url 将不会进行 gzip 压缩。
gzip_proxied:Nginx 做为反向代理的时候启用:
? off – 关闭所有的代理结果数据压缩
? expired – 如果 header 中包含 “Expires” 头信息,启用压缩
? no-cache – 如果 header 中包含 “Cache-Control:no-cache” 头信息,启用压缩
? no-store – 如果 header 中包含 “Cache-Control:no-store” 头信息,启用压缩
? private – 如果 header 中包含 “Cache-Control:private” 头信息,启用压缩
? no_last_modified – 启用压缩,如果 header 中包含 “Last_Modified” 头信息,
启用压缩
? no_etag – 启用压缩,如果 header 中包含 “ETag” 头信息,启用压缩
? auth – 启用压缩,如果 header 中包含 “Authorization” 头信息,启用压缩
? any – 无条件压缩所有结果数据
Syntax: gzip_proxied off | expired | no-cache | no-store | private | no_last_modified | no_etag | auth | any ...;
Default: gzip_proxied off;
Context: http, server, location
gzip_types:设置需要压缩的 MIME 类型,如果不在设置类型范围内的请求不进行压缩。匹配 MIME 类型进行压缩,(无论是否指定)"text/html"类型总是会被压缩的。
Syntax: gzip_types mime-type ...;
Default: gzip_types text/html;
Context: http, server, location
gzip_vary:增加响应头 "Vary: Accept-Encoding"告诉接收方发送的数据经过了压缩处理,开启后的效果是在响应头部添加了 Accept-Encoding:gzip,这对于本身不支持 gzip 压缩的客户端浏览器有用。
Syntax: gzip_vary on | off;
Default: gzip_vary off;
Context: http, server, location