在基于 HTTP 协议的网络传输中 GZip 常常被使用,Nginx 中也可使用半行代码开启 GZip。GZip 压缩的原理是什么呢?本篇文章是我在网上阅读了一些文档后作的简单总结。html
RFC 1952 是 GZIP file format specification version 4.3
。该规范主要定义了 GZip 压缩的在数据格式方面的规范,以方便不一样的操做系统、CPU、文件系统等之间进行文件传输交换。下面挑有意思的几个点说,感兴趣的能够阅读 RFC 1952 的原文。nginx
GZIP 的文件格式在设计上实际上是能够容许一个文件里有多个压缩数据集(compressed data sets)—— GZIP 压缩后的片断拼接而成的。但就咱们大多数应用场景来讲,基本上都是一个文件一个压缩数据集,若是是多个文件一块儿打包的话,也每每是将多个包合并成一个 tar 文件。git
每一个压缩数据集都是下面的结构:github
| ID1 | ID2 | CM | FLG | MTIME(4字节) | XFL | OS | ---> moreweb
|
与|
之间是 1 byte,都是大端字节(Big Edian)算法
其中 ID1 和 ID2 分别是 0x1f 和 0x8b,用来标识文件格式是 gzip浏览器
CM 标识 加密算法,目前 0-7是保留字,8 指的是 deflate 算法bash
FLG 从低地址到高地址分别是 FTEXT、FHCRC、FEXTRA、FNAME、FCOMMENT、reserved、 reserved、reserved,这里每一个 bit 被设置了以后有什么意义感兴趣的话能够详细参考 RFC 1952。比较有意思的是 FEXTRA,若是它被设置了表示存在额外的拓展字段。拓展字段的结构以下:服务器
<gzip@prep.ai.mit.edu>
发邮件申请。目前 Apollo file 就有本身的专属 IDMTIME 指的是源文件最近一次修改时间,存的是 Unix 时间戳网络
XFL 是给压缩算法传的一些参数,用来标识如何解压。defalte 算法中 2 表示使用压缩率最高的算法,4 表示使用压缩速度最快的算法
OS 标识压缩程序运行的文件系统,以处理 EOF 等的问题
more 后面是根据 FLG 的开启状况决定的,可能会有 循环冗余校验码、源文件长度、附加信息等多种其余信息
GZIP 的核心是 Deflate,在 RFC 1951 中被标准化,而且在当时做为 LZW 的替代品有了很是普遍的使用。
Deflate 是一个同时使用 LZ77 与 Huffman Coding 的算法,这里简单介绍下这两种算法的大体思路:
LZ77 的核心思路是若是一个串中有两个重复的串,那么只须要知道第一个串的内容和后面串相对于第一个串起始位置的距离 + 串的长度。
好比: ABCDEFGABCDEFH → ABCDEFG(7,6)H。7 指的是往前第 7 个数开始,6 指的是重复串的长度,ABCDEFG(7,6)H 彻底能够表示前面的串,而且是没有二义性的。
LZ77 用 滑动窗口(sliding-window compression)来实现这个算法。具体思路是扫描头从串的头部开始扫描串,在扫描头的前面有一个长度为 N 的滑动窗口。若是发现扫描头处的串和窗口里的 最长匹配串 是相同的,则用(两个串之间的距离,串的长度)来代替后一个重复的串,同时还须要添加一个表示是真实串仍是替换后的“串”的字节在前面以方便解压(此串须要在 真实串和替换“串” 以前都有存在)。
实际过程当中滑动窗口的大小是固定的,匹配的串也有最小长度限制,以方便 标识+两个串之间的距离+串的长度 所占用的字节是固定的 以及 不要约压缩体积越大。更加详细的实现能够参考:Standford Edu. lz77 algorithm、 LZ77 Compression Algorithm、 LZ77压缩算法编码原理详解(结合图片和简单代码)
这里经过这个压缩机制也就能比较容易的解释为啥 CSS BEM 写法 GZIP 压缩以后能够忽略长度以及 JPEG 图片 GZIP 以后可能会变大 的状况了
解压:GZIP 的压缩由于要在窗口里寻找重复串相对来讲效率是比较低的(LZ77 仍是经过 Hash 等系列方法提升了不少),那解压又是怎么个状况呢?观察压缩后的整个串,每一个小串前都有一个标识要标记是原始串仍是替换“串”,经过这个标识就能以 O(1)的复杂度直接读完而且替换完替换“串”,总体上效率是很是可观的。
Huffman Coding 是大学课本中通常都会提到的算法。核心思路是经过构造 Huffman Tree 的方式给字符从新编码(核心是避免一个叶子的路径是另一个叶子路径的前缀),以保证出现频路越高的字符占用的字节越少。关于 Huffman Tree 的构造这里再也不细说,不太清楚的能够参考:Huffman Coding。
解压:Huffman Coding 以后须要维护一张 Huffman Map 表,来记录从新编码后的字符串,根据这张表,还原原始串也是很是高效的。
Deflate 综合使用了 LZ77 和 Huffman Coding 来压缩文件,相对而言又提高了不少。详细能够参考 gzip原理与实现
在 RFC 2016 中 GZIP 已经成为了规定的三种标准HTTP压缩格式之一。目前绝大多数的网站都在使用 GZIP 传输 HTML、CSS、JavaScript 等资源文件。
Nginx 的 ngx_http_gzip_module 也提供了开启 GZIP 压缩的方式,有下面的一些经常使用配置:
# 开启
gzip on;
# 压缩等级,1-9。设置多少能够参考:http://serverfault.com/questions/253074/what-is-the-best-nginx-compression-gzip-level
gzip_comp_level 2;
# "MSIE [1-6]\." 好比禁止 IE6 使用 GZIP
gzip_disable regex ...
# 最小压缩文件长度
gzip_min_length 20;
# 使用 GZIP 压缩的最小 HTTP 版本
gzip_http_version 1.1;
# 压缩的文件类型,值是 [MIME type](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types)
gzip_types text/html;
复制代码
Nginx 上开启 GZIP 以后,理论上会按照 GZIP 配置打开压缩。那如何检测是否开启成功了呢?
打开浏览器,访问你的网站,看 Chrome 的 Network,若是 Size 上有两个不同大小的体积(如:222KB 和 613KB),则表明 GZIP 已经成功开启。
那浏览器又是如何和服务器配合的呢?
浏览器在请求资源的时候再 header 里面带上 accept-encoding: gzip
的参数。Nginx 在接收到 Header 以后,发现若是有这个配置,则发送 GZIP 以后的文件(返回的 header 里也包含相关的说明),若是没有则发送源文件。浏览器根据 response header 来处理要不要针对返回的文件进行解压缩而后展现。