nginx使用gzip压缩文件---lz77算法---Haffman编码

为了提高页面的响应速度,可以从设置 nginx 的 gzip 和缓存这2方面入手,而为ttf,js,css等文件开启 gzip 和缓存能大大减少带宽的消耗.

HTTP 的内容编码机制

Accept-Encoding 和 Content-Encoding 是 HTTP 中用来对[采用何种编码格式传输正文]进行协定的一对头部字段.
它的工作原理是这样:

浏览器发送请求时,通过 Accept-Encoding 带上自己支持的内容编码格式列表;
服务端从中挑选一种用来对正文进行编码,并通过 Content-Encoding 响应头指明选定的格式;
浏览器拿到响应正文后,依据 Content-Encoding 进行解压.
当然,服务端也可以返回未压缩的正文,但这种情况不允许返回 Content-Encoding;
这个过程就是 HTTP 的内容编码机制.

Accept-Encoding,作为请求首部字段,可以一次性指定多种内容编码,比如:

1.gzip
	由文件压缩程序gzip(GNU zip)生成的编码格式(RFC1952),采用Lempel-Ziv算法(LZ77)及32位冗余校验(Cyclic Redundancy Check,统称CRC);
	文章后面会统一介绍gzip的算法;
2.compress
	由UNIX文件压缩程序compress生成的编码格式,采用Lempel-Ziv-Welch算法(LZW);
3.deflate
	组合使用zlib格式(RFC1950)及由default压缩算法(RFC1951)生成的编码格式;
4.identity
	不执行压缩或者不会变化的默认编码格式.

Content-Encoding作为实体首部字段,采用的内容编码格式和Accept-Encoding是相对应的.

在nginx上添加以下逻辑,用来压缩字体文件,以达到节省网络带宽,提高网站速度的作用.

	#字体有很多格式,为匹配字体格式的文件进行压缩设置
	#就是拦截这种请求,然后压缩:www.test.com/dist/aabbccddeeffgg.ttf
	location ~* ^/dist/.+\.(eot|ttf|otf|woff|svg)$ {
        #不缓存
        expires                 0;
        
        #增加type对应类型
        types {
            application/vnd.ms-fontobject eot;
            font/ttf ttf;
            font/opentype otf;
            font/x-woff woff;
            image/svg+xml svg;
        }
        # 开启gzip
        gzip  on;
        # 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
        gzip_min_length 1k;
        # 设置压缩所需要的缓冲区大小
        gzip_buffers 4 16k;
        #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
        gzip_http_version 1.0;
        #压缩等级 1-9 等级越高,压缩效果越好,节约宽带,但CPU消耗大
        gzip_comp_level 3;
        # 进行压缩的文件类型,默认就已经包含text/html。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
        gzip_types font/ttf font/opentype font/x-woff image/svg+xml;
        # 是否在http header中添加Vary: Accept-Encoding,建议开启
        gzip_vary on;
        # 禁用IE 6 gzip , 因为IE6的某些版本对gzip的压缩支持很不好,会造成页面的假死
        gzip_disable "MSIE [1-6].";
    }

测试结果:

这里写图片描述

用curl测试gzip是否开启成功

curl -I -H "Accept-Encoding: gzip, deflate" "http://www.test.com/dist/aabbccddeeffgg.ttf"
gzip未开启:

TP/1.1 200 OK
Server: JDWS/1.0.0
Date: Tue, 21 Aug 2018 01:49:24 GMT
Content-Length: 18520
Connection: close
Last-Modified: Mon, 20 Aug 2018 09:40:38 GMT
Accept-Ranges: bytes
Expires: Tue, 21 Aug 2018 01:49:24 GMT
Cache-Control: max-age=0
gzip开启:

TP/1.1 200 OK
Server: JDWS/1.0.0
Date: Tue, 21 Aug 2018 01:48:10 GMT
Content-Type: font/ttf
Last-Modified: Fri, 10 Aug 2018 06:13:07 GMT
Connection: close
Vary: Accept-Encoding
Expires: Tue, 21 Aug 2018 01:48:10 GMT
Cache-Control: max-age=0
Content-Encoding: gzip

gzip压缩算法:

gzip是一种数据压缩格式,或者说是一种文件格式,用.gz结尾;
gzip可以极大的加速网站.有时压缩比率高达80%,一般最少都有40%左右;
对于要压缩的文件,使用gzip,主要分为两步:
1.使用LZ77压缩算法进行压缩,得到初始结果;
2.使用Haffman编码对初始结果进行再压缩,得到最终结果.

详细说下这两种算法:

LZ77算法

	这个算法是由Jacob Ziv 和 Abraham Lempel 于 1977 年提出,所以命名为 LZ77.

整体思路:
	我们认为是一段内容总会有重复的内容,后面重复的内容可以用两者之间的距离加内容长度联合替换,
	而替换后的内容的所占空间一般都会比原内容小,重复越多,压缩完空间就越小.
	
详细实现过程:
	先介绍三个概念:
		1.待编码区(字符从这里进来);
		2.已编码区(字符从这里出去,也叫缓冲区);
		3.滑动窗口(左边是已编码区,右边是待编码区,字符会从右向左依次被读取).
		|        滑动窗口       |
		|  已编码区   | 待编码区 |
		|             | a   a   b| c  b  b  a  b  c 
		
	再看看大概步骤:
		1.字符a从待编码区进来,此时a在待编码区的最左边;
		2.在待编码区查找从这个a字符在已编码区的最大匹配长度;
		3.如果能找到,就输出(偏移量m,最大匹配长度n),滑动窗口向右偏移n个位置;
			偏移量m就是待编码区的字符a到已编码区匹配到的字符a的移动距离;
			最大匹配长度n就是待编码区有多少连续字符可以在已编码区找到匹配的,这些连续字符的长度;
		4.如果找不到,就输出(0,0,a),滑动窗口向右偏移1位;
		5.直到待编码区为空,就停止编码,否则继续从2开始循环.

以上面的字符串为例,举个简单易懂的例子:
第一步:
a到待编码区的最左边了,在已编码区没有找到匹配的字符,输出(0,0,a),
如下图所示:
这里写图片描述
滑动窗口准备右移一位,到第二步.
第二步:
开始编码,待编码区的第一个字符是a,在已编码区查找匹配字符,找到了;
继续在待编码区读到ab两个字符,在已编码区查找匹配字符,没找到;
所以本次编码待编码区的第一个字符a,输出(1,1);
第一个1代表待编码区的a到已编码区的偏移量,偏移一位,记为1;
第二个1代表本次可编码的长度,因为只有一个a,所以记为1.
如下图所示:
这里写图片描述
滑动窗口准备右移一位,到第三步.
第三步:
待编码区第一个字符是b,在已编码区查找匹配字符,没找到;
同第一步,输出(0,0,b).
如下图所示:
这里写图片描述
滑动窗口准备右移一位,到第四步.
第四步:
待编码区第一个字符是c,在已编码区查找匹配字符,没找到;
同第一步,输出(0,0,c).
如下图所示:
这里写图片描述
滑动窗口准备右移一位,到第五步.
第五步:
待编码区第一个字符是b,在已编码区查找匹配字符,找到了;
待编码区前两个字符是bb,在已编码区查找匹配字符,没找到;
所以本次编码待编码区的第一个字符b,输出(2,1).
如下图所示:
这里写图片描述
滑动窗口准备右移一位,到第六步.
第六步:
待编码区第一个字符是b,在已编码区查找匹配字符,找到了;
待编码区前两个字符是ba,在已编码区查找匹配字符,没找到;
所以本次编码待编码区的第一个字符b,输出(3,1).
如下图所示:
这里写图片描述
滑动窗口准备右移一位,到第七步.
第七步:
待编码区第一个字符是a,在已编码区查找匹配字符,找到了;
待编码区前两个字符是ab,在已编码区查找匹配字符,找到了;
待编码区前三个字符是abc,在已编码区查找匹配字符,找到了;
这个例子待编码区只有三个字符的长度,实际会很长,就一直找,直到找不到匹配字符为止;
所以本次编码待编码区的前三个字符abc,输出(5,3).
如下图所示:
这里写图片描述
滑动窗口准备右移三位,到第八步.
第八步:
待编码区没有字符了,结束.
这里写图片描述

最终结果:
(0,0,a)(1,1)(0,0,b)(0,0,c)(2,1)(3,1)(5,3)
通过这个结果反向解码,也能得到原文:
aabcbbabc
而且解码比编码还要快很多,主要是因为少了匹配这一步.

Haffman编码

整体思路:
	我们知道,普通的编码都是定长的,比如ASCII编码,每个字符编码后的长度都是固定长度,
在解码的时候也就相对简单,只需按照定长将码解开,逐个翻译成对应的字符即可;
	而haffman采用的是可变长编码,目的是为了让出现次数多的字符采用更少的长度来存储,
这样就可能会大大加强压缩力度;
	haffman建立了树的概念,haffman树是一种二叉树,将所有符号都对应到树的叶子节点上,
每个叶子节点都是可以唯一表示的,都是用若干个0和1组成的;
	由于所有的字符最终都会落在叶子节点上,即任何字符的编码都不会是其他字符编码的前缀,
所以在解析的时候,也不会发生混淆的问题,因此haffman编码后的码,也是一种唯一可译码.
	
详细实现过程:
	1.先读所有文件,将所有内容中所有字符出现的次数统计出来做排序;
	2.我们将出现少的两个字符作为子节点,可以确定一个父节点,值就是两个子节点的次数和;
	3.将上一步的父节点当做一个子节点,再重复执行第二步,直到所有节点全部用完,构成一颗大树;
	4.每个数从父节点开始,默认左边的子节点是0,右边的子节点是1,
	  那么所有的字符对应的子节点都会有唯一的标识了;
	5.用这些唯一标识来代替原来的字符,就是编码后的结果;
	6.解码的时候,也是先根据所有字符的次数构成那颗大树,然后根据大树找出对应的字符即可.

举个简单易懂的例子:
比如文件的内容是:abbbbccccddde
我们可以统计出:
a出现1次;
b出现4次;
c出现4次;
d出现3次;
e出现1次;
第一步:
找出所有字符出现的次数.
如下图所示:
这里写图片描述
第二步:
找出两个最小次数的字符,构成一颗树;
a字符和e字符都只出现1次,次数最少,构成一颗树;
产生新的节点,个数为a和e次数的和,即为1+1=2.
如下图所示:
这里写图片描述
第三步:
再找出剩下的节点中出现次数最小的两个节点;
找出d字符和上一步的新节点,分别一个是3次,一个是2次;
这两个节点继续壮大这颗树,再产生新的节点,次数为3+2=5次.
如下图所示:
这里写图片描述
第四步:
再从剩下的节点中找出最小的两个,是b和c
构成新的树,父节点是4+4=8
如下图所示:
这里写图片描述
第五步:
将最后两个节点合并成一颗大树;
最上面是树的根节点;
最下面是每个字符对应的子节点;
我们默认为:从根节点自上向下看开始计数,往左拐记为0,往右拐记为1;
那么从根节点到下面每个子节点的路径所对应的计数的组合,就是这个字符所要代替的01组合.
如下图所示:
这里写图片描述
最终不难看出:
a对应110
b对应00
c对应01
d对应10
e对应111
那么原内容:abbbbccccddde
就可以替换为:1100000000001010101101010111
这个就是压缩后的内容,
那么在解压的时候,再将这些10的组合依次替换成对应的字符即可.

猜你喜欢

转载自blog.csdn.net/java_zhangshuai/article/details/81749208