数据流压缩之应用篇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. 头部分

ID1ID2:各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库(数据压缩与解压)

C++ 基于ZLIB压缩库的数据或文件的压缩与解压缩小程序

Ruby zlib 库解压 gzip 文件非常慢

Qt之zip压缩/解压缩(QuaZIP)

Swoole WebSoctet 使用 zlib 压缩之 PHP 与 pako.js

Delphi 使用 ZLib 压缩和解压 GZip