zlib简介

Fri 03 August 2018 / In categories programming

gzip, zip, zlib

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上这个帖子的说明,打上下面的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好

其他参考

Load Disqus Comments