zlib是一个非常通用的文件压缩库,包含在多种语言的标准库中,如python的zlib库,go语言的compression/zlib

概述

zlib通用是因为它的算法和格式都是公开的,发布在IETF的RFC中

在使用zlib之前,有几个相关的概念要搞清楚:

  • zlib是一种数据格式,用于存储压缩后的数据
  • gzip也是一种数据格式,也用于用于存储压缩后的数据,不过gzip默认保存的是单个文件
  • deflate是压缩算法,zlib和gzip都采用它来压缩文件
  • inflate是与deflate对应的解压缩算法

关于对概念更多地解释,请参考How are zlib, gzip and zip related? What do they have in common and how are they different?

不要把zlib和zip搞混了。.zip是一种归档文件格式,多个经过zlib压缩的文件可以存放到一个.zip文件中。zlib本身不支持.zip,但是基于zlib的libzip支持.zip,可以读写存放于其中的文件。

一点小历史

根据gzip.org主页的说明。一开始Unix上的压缩程序是compress,经过它压缩的文件以.Z结尾。但是compress使用的LZW算法是受专利保护的。于是乎没有专利问题的gzip出现了,用来替代compressgzipcompress一样,只能压缩单个文件,但是gzip压缩的文件以.gz结尾。gzip的作者们后来又开发了zlib,使其更通用化,作为程序库可以被其他更多的程序使用。

下载并编译zlib

zlib的源码同步在GitHub上,可以直接用Git克隆下来。

zlib同时提供了automake和cmake两种构建方式。使用automake的话:

./configure
make

如果使用cmake的话:

mkdir build
cd build
cmake ..
make

Cygwin环境下的问题

我是编译环境是Cygwin,编译的时候出了一个小问题,说是undefined reference to '_wopen'。根据StackOverflow上[这个帖子](Compiling gcc-7-20170212 on Windows 7-64 with cygwin, ==> wopen error)的说明,打上下面的patch就可以正常编译了。

--- zlib-1.2.11/gzguts.h.orig	2017-01-25 07:59:56.957392500 +0300
+++ zlib-1.2.11/gzguts.h	2017-01-25 08:00:03.311603900 +0300
@@ -39,7 +39,7 @@
 #  include <io.h>
 #endif
 
-#if defined(_WIN32) || defined(__CYGWIN__)
+#if defined(_WIN32)
 #  define WIDECHAR
 #endif

使用automake的话,只会把zlib编译成静态库libz.a;使用cmake的话,除了libz.a之外还会尝试编译动态链接库,但是会失败,好像在Cygwin环境里面不支持。

zlib的代码目录

zlib的代码目录中包含了许多有价值的信息:

  • FAQ 记述了一些常见的问题
  • doc/目录包含一些文档,其中algorithm.txt对deflate算法进行了描述
  • zlib.h包含了完整的api说明,内容和zlib manual差不多。
  • test/example.c 是一个例子,说明怎么使用zlib
  • exmaples/目录中包含更多例子可供学习
  • contrib/minizip包含了一个叫minizip的程序的源码,可以用来操作.zip文件。但是要注意的是,contrib目录中的内容版权归第三方所有,不包括在zlib的license里面。

使用zlib提供的API

zlib的核心数据结构是z_stream,用来表示比特流,同时用于deflate或者inflate过程。以deflate过程为例,z_stream必须使用deflateInit()函数初始化。但是值得注意的是,在调用deflateInit()之前,需要先把z_stream数据结构中的三个属性初始化,分别是:zalloc, zfreeopaque。这三个参数是给zlib的应用程序制定内存分配器用的,如果想让zlib使用系统默认的内存分配器,将这三个参数设为无效值即可,比如Z_NULL

deflateInit()带有两个参数,一个是需要初始化的z_stream,另一个是level,用于制定压缩等级,值可以从1到9,-1表示使用默认的压缩等级,大概是6。level值越大,压缩比越高,但是运行速度越低。

z_stream不再使用的时候,要使用deflateEnd()来释放内部的数据结果。

deflate()函数是deflate过程中的核心函数,经常需要多次调用,这个函数带两个参数z_streamflush,使用的时候有以下注意点:

  • 调用deflate()前要初始化z_stream的以下属性:
    • next_in, 下一个输入字节之所在
    • avail_innext_in还有多少字节可以输入
    • next_out, 能够存储下一个输出字节的起始地址
    • avail_outnext_out还有多少字节空间可以用来保存输出字节
  • 应用程序必须确保deflate()可以从next_in中读到数据,或者从next_out中写入数据,否则会返回Z_STREAM_ERROR
  • 如果deflate()无法读取或者写入数据,会返回Z_BUF_ERROR
  • 正常的情况下,当deflate()处理了一些数据后会返回Z_OK
  • deflate()读取了所有输入数据,然后这些数据都写入输出了之后,deflate()会返回Z_STREAM_END
  • 通过flush参数可以控制deflate()的输出过程
    • 设为Z_NO_FLUSH的话,输出多少数据由deflate()决定
    • 设为Z_FULL_FLUSH的话,deflate()会把所有的处理好的数据输出,并且重置状态。经常使用Z_FULL_FLUSH会影响性能。
    • 设为Z_FINISH的话,告诉deflate()所有输入数据都已经提供,可以结束处理了。deflate()会把剩余的结果输出,然后结束处理过程。之后,只能调用deflateReset来重置内部状态,或者deflateEnd来释放内部状态。
    • 其他选项,比如:Z_SYNC_FLUSHZ_PARTIAL_FLUSHZ_BLOCK属于高级用法,就不描述了,可以参考文档
    • 有一点需要注意,当输出空间不足,也就是avail_out为空的时候,再次调用deflate()的时候必须提供更多输出空间,以及保持flush标志不变。

与defalte类似,inflate也有对应的:inflateInit()inflate()inflateEnd()

除了deflate和inflate对应的函数接口以外,zlib还提供了一些封装了这些接口的实用函数,比如:compress()uncompress()。这些函数简化了deflate和inflate函数接口的使用。

deflate的一个例子

下面的例子摘自于zpipe.c

/* Compress from file source to file dest until EOF on source.
   def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
   allocated for processing, Z_STREAM_ERROR if an invalid compression
   level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
   version of the library linked do not match, or Z_ERRNO if there is
   an error reading or writing the files. */
int def(FILE *source, FILE *dest, int level)
{
    int ret, flush;
    unsigned have;
    z_stream strm;
    unsigned char in[CHUNK];
    unsigned char out[CHUNK];

    /* allocate deflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    ret = deflateInit(&strm, level);
    if (ret != Z_OK)
        return ret;

    /* compress until end of file */
    do {
        strm.avail_in = fread(in, 1, CHUNK, source);
        if (ferror(source)) {
            (void)deflateEnd(&strm);
            return Z_ERRNO;
        }
        flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
        strm.next_in = in;

        /* run deflate() on input until output buffer not full, finish
           compression if all of source has been read in */
        do {
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = deflate(&strm, flush);    /* no bad return value */
            assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
            have = CHUNK - strm.avail_out;
            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
                (void)deflateEnd(&strm);
                return Z_ERRNO;
            }
        } while (strm.avail_out == 0);
        assert(strm.avail_in == 0);     /* all input will be used */

        /* done when last data in file processed */
    } while (flush != Z_FINISH);
    assert(ret == Z_STREAM_END);        /* stream will be complete */

    /* clean up and return */
    (void)deflateEnd(&strm);
    return Z_OK;
}

实用工具pigz

pigz是zlib的作者之一Mark Adler编写的实用工具,支持zlib格式以及gzip格式。根据StackOverflow这篇帖子How to uncompress zlib data in UNIX?,pigz的用法如下:

  • pigz -z test,从test文件创建一个zlib格式(由命令行选项-z指定)的压缩文件,以.zz结尾
  • pigz -d -z test.zz,从test.zz恢复出test文件

其他文件压缩相关的库

zlib替代

  • miniz, 一个zlib的小型替代,实现了zlib的基本API(包括部分utility相关的API,但不包括gzip相关的)。此外对zip文件的读写提供了一些支持。优点是它的release是一个头文件加上一个C语言原文件,易于集成,没有太多依赖。
  • LZMA SDK,7zip提供的压缩库。xz是它的一个改进项目。

重视运行速度但是不重视压缩率

  • snappy, 谷歌开源的文件压缩库,重视速度
  • lzo,运行速度快,特别是解压文件的时候。提供miniLZO,方便应用程序集成。
  • lz4, 另一款速度飞起的压缩库

重视压缩率但是不重视运行速度

  • bzip, bzip2 and libbzip2,重压缩比,不重速度。我们下载文件的时候很多都是用这种格式压缩的,文件后缀以.bz2结尾。

运行速度和压缩速率都重视

  • brotli, 谷歌开源的文件压缩库,中高压缩比下表现比zlib好
  • zstd, 脸书开源的文件压缩库,中高压缩比下表现比zlib好

其他参考

2022-12-31更新

2015年的这篇Improving compression with a preset DEFLATE dictionary介绍了使用Dictionary来提高压缩率,并提供了一个工具https://github.com/vkrasnov/dictator

https://stackoverflow.com/questions/2011653/how-to-find-a-good-optimal-dictionary-for-zlib-setdictionary-when-processing-a提到了https://github.com/madler/infgen这个工具。

https://stackoverflow.com/questions/67632095/using-a-preset-deflate-dictionary-to-reduce-compressed-archive-file-size提到:

There is nothing special about the construction of a dictionary. All it is is 32K bytes of strings that you believe will occur often in the data you are compressing. You should put the most common strings at the end of the 32K.

https://stackoverflow.com/questions/17608271/how-to-compute-good-preset-dictionary-for-deflate-compression提到并没有现成好用的办法,来生成字典。

https://stackoverflow.com/questions/21712250/is-it-possible-to-create-valid-gzip-with-static-dictionary对zlib中Huffman code的使用做了一些解释。

Understanding zlib是一部讲解zlib的在线电子书。

zlib的.NET实现

(更新完)