数据流压缩之应用篇zlib库
Posted sesiria
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据流压缩之应用篇zlib库相关的知识,希望对你有一定的参考价值。
关于数据流压缩的原理,lz77以及huffman编码可以参考上一篇:
https://blog.csdn.net/sesiria/article/details/116835301
本篇将包含以下内容:
1. gzip格式分析
2. zlib库函数API分析
3. zlib库实战(压缩和解压文件)
gzip格式采用deflate算法来实现数据的压缩
一、deflate采用了改进版的lz77算法
即:三个字节以上重复才进行编码,否则不进行编码;即对滑动窗口进行查询的时候最短的匹配大小为3个字节。
为什么要最小匹配3个字节呢? 这是由于,在gzip中,<匹配长度,到匹配串开头的距离>对中,“匹配长度”的范围为3-258,也就是有256种可能指,需要8bit来保存。 “到匹配 串开头的距离"范围 为0~32k,需要15bit来保存。 所以一个<匹配长度,到匹配串开头的距离>对需要23位,差一位3个字节。
如果一个匹配串小于3个字节,使用<匹配长度,到匹配串开头的距离>对进行替换,不但没有压缩,反而还会增大。所以保存<匹配长度,到匹配串开头的距离>所队应的位数,决定了最小匹配长度至少要为3个字节。
deflate无损压缩解压算法(先lz77压缩,然后huffman编码)
deflate中的huffman编码:
对lz77得到的压缩后结果,需要统计字符生成编码表huffmantree,根据码表对内容进行编码,从而实现压缩的效果。
编码表hufmantree和编码后的data都放在一个文件中。
deflate中欧弄个的解压:
读取二进制文件,构建huffmantree表,读取数据根据huffmantree生成字符
接着用lz77解码,进行搜索匹配并替换为队应的串。
todo: 基于先使用lz77 最短3字节编码,后使用huffman编码的deflate压缩算法的实现:
二、gzip格式分析
gzip的压缩原理:先使用lz77算法的 一个变种进行压缩,对得到的结果再使用huffman编码进行压缩;
bzip2的压缩原理:使用了一个游程编码器进行编码,接下来块排序压缩和Move-to-front(MTF)变换进一步产生大量相同符号,进一步使用另一个游程编码器进行编码。最后使用huffman编码,将一个消息头与其打包;
LZMA编码:它是deflate和lz77算法改良和优化后的压缩算法,而Deflate是同时使用了lz77算法与huffman编码的一个无损 数据压缩算法。
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个块。每个块包含头,数据和尾三部分。
块的大概结构如下:
1. 头部分
ID1与ID2:各1字节。固定值,ID1 = 31(0x1F), ID2 = 139(0X8B), 指示GZIP格式。
CM: 1字节。表示压缩方法。目前只有一种: CM = 8, 指Deflate算法。
FLG: 1字节,标志位。
bit 0 FTEST - 指示文本数据
bit 1 FHCRC - 指示存在CRC16头校验字段
bit 2 FEXTRA - 指示存在可选字段
bit 3 FNAME - 指示存在原文件名字段
bit 4 FCOMMENT - 指示存在注释字段
bit 5-7 保留
MTIME: 4字节。更改时间。UNIX格式。
XLF: 1字节。 附加的标志。当CM=8时, XFL = 2 表示采用最大压缩但最慢的算法;
XFL=4 表示最快但最小的压缩算法
OS: 1字节。操作系统,确切说应该是文件系统。有如下定义:
0 - FAT文件系统 (MS-DOC, OS/2, NT/Win32)
1 - Amiga
2 - VMS/OpenVMS
3 - Unix
4 - VM/CMS
5 - Atari TOS
6 - HPFS 文件xitong (OS/2, NT)
7 - Macintosh
8 - Z-System
9 - CP/M
10 - TOPS-20
11 - NTFS
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文件格式的额外数据
2. 数据部分
Deflate数据格式,包含一些列子数据块。子块结构如下:
+......+......+......+=============//============+
|BFINAL| BTYPE | 数据 |
+......+......+......+=============//============+
BFINAL: 1比特。 0 - 还有后续子快; 1- 改子快是最后一块。 BTYPE: 2比特。
00 - 不压缩; 01 - 静态Huffman编码压缩; 10 - 动态Huffman编码压缩; 11 - 保留。
3. 尾部分
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
3. 基础数据结构( zlib.h)
z_stream_s
typedef struct z_stream_s {
z_const Bytef *next_in; /* next input byte */
uInt avail_in; /* number of bytes available at next_in */
uLong total_in; /* total number of input bytes read so far */
Bytef *next_out; /* next output byte will go here */
uInt avail_out; /* remaining free space at next_out */
uLong total_out; /* total number of bytes output so far */
z_const char *msg; /* last error message, NULL if no error */
struct internal_state FAR *state; /* not visible by applications */
alloc_func zalloc; /* used to allocate the internal state */
free_func zfree; /* used to free the internal state */
voidpf opaque; /* private data object passed to zalloc and zfree */
int data_type; /* best guess about the data type: binary or text
for deflate, or the decoding state for inflate */
uLong adler; /* Adler-32 or CRC-32 value of the uncompressed data */
uLong reserved; /* reserved for future use */
} z_stream;
gz_header
/*
gzip header information passed to and from zlib routines. See RFC 1952
for more details on the meanings of these fields.
*/
typedef struct gz_header_s {
int text; /* true if compressed data believed to be text */
uLong time; /* modification time */
int xflags; /* extra flags (not used when writing a gzip file) */
int os; /* operating system */
Bytef *extra; /* pointer to extra field or Z_NULL if none */
uInt extra_len; /* extra field length (valid if extra != Z_NULL) */
uInt extra_max; /* space at extra (only when reading header) */
Bytef *name; /* pointer to zero-terminated file name or Z_NULL */
uInt name_max; /* space at name (only when reading header) */
Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */
uInt comm_max; /* space at comment (only when reading header) */
int hcrc; /* true if there was or will be a header crc */
int done; /* true when done reading gzip header (not used
when writing a gzip file) */
} gz_header;
4. 常用的函数
压缩函数
deflateInit : 参数比较少,里面的实现其实是调用deflateInit2
deflateInit2: 压缩初始化的基础函数,有很多参数。
deflate: 压缩函数
deflateEnd: 压缩完成以后,释放空间,仅仅是释放deflateInit中申请的空间,自己申请的空间还是要自己释放
compress : 全部附加选项默认压缩,内部调用compress2
compress2 : 可选择压缩level的压缩方式
压缩函数介绍
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 是这个压缩的上下文,依照官方给的例子进行初始化
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: 值只有一个,当前唯一的deflate压缩方法,用于以后的扩展
#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
stragegy:用于调整压缩算法,直接给默认就行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 */
deflate:压缩函数
ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
deflate函数金可能的压缩数据,当输入缓冲为空或者输出缓冲满了则会停止,它会带来输出延迟(读入的数据没有立即输出),除非强行刷新缓冲区。
详细的语意如下,deflate会执行下面的一个或者两个动作:
1) 从next_in开始压缩输入数据从而更新next_in和avail_in. 如果不是所有输入数据都可以被处理(比如因为缓冲区没有足够的空间),next_in和avail_in会更新,当再次调用deflate()函数时输入数据会从这一点开始被处理。
2) 从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: 将所有等待输出的数据刷新到输出缓冲区,以字节为边界进行对齐。该刷新可能会降低压缩算法的压缩效率,它只用于必要的时候。
Z_FINISH: 如果输出和待输出的数据都被处理完,则返回Z_STREAM_END. 如果返回Z_OK 或者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函数中以后,初始化三个回调函数以后即可
windowBits: 函数定义和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));
四,zlib实例分析
使用zlib封装库进行文件的压缩与解压
https://github.com/sesiria/Algs/tree/master/Lib/CompressLibrary
deflate 实现了使用gzip格式进行数据的压缩 deflate.cpp
inflate 实现了解压缩gzip格式
五、nginx优化之gzip压缩提升网站速度
注意:图片、视频、音频等二进制文件没必要进行压缩
配置nginx的配置文件
# 开启 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 memorypage)大小以 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"告诉接收方发送的数据经过了压缩处理,开启后的效果是在响应头部添加了 AcceptEncoding:gzip,这对于本身不支持 gzip 压缩的客户端浏览器有用。
Syntax: gzip_vary on | off;
Default: gzip_vary off;
Context: http, server, location
以上是关于数据流压缩之应用篇zlib库的主要内容,如果未能解决你的问题,请参考以下文章
Linux(程序设计):29---Zlib库(数据压缩与解压)