zlib使用教程:从入门到精通 – wiki词典


zlib 使用教程:从入门到精通

引言

在数据传输和存储日益增长的今天,高效的数据压缩技术变得至关重要。zlib 是一个广泛使用的、开源的、跨平台的无损数据压缩库。它实现了 DEFLATE 压缩算法,该算法也广泛应用于 gzip 格式。zlib 因其高度的可移植性、低内存占用以及其压缩格式不受专利限制而闻名,被广泛应用于各种应用程序、操作系统和网络协议中。

本教程将带你从 zlib 的基本概念入手,逐步深入到其高级特性,并提供 C/C++ 和 Python 语言的实际代码示例。

Zlib, Gzip, 和 Raw Deflate 的区别

理解这三者之间的关系是使用 zlib 的基础:

  • DEFLATE: 这是纯粹的压缩算法本身,不包含任何文件头或文件尾信息。
  • Zlib Format: zlib 格式在 DEFLATE 数据流的外部添加了一个小的文件头(CMF, FLG 字节)和一个文件尾(Adler-32 校验和)。它由 RFC 1950 定义。
  • Gzip Format: gzip 格式(由 RFC 1952 定义)拥有比 zlib 格式更大的文件头和文件尾,包含了文件名、修改时间等元数据,并使用 CRC-32 校验和。gzip 通常用于单文件压缩。

zlib 库能够处理这三种格式,但其默认的内存内(in-memory)函数通常使用 zlib 格式。

入门:C/C++ 基本压缩与解压缩

zlib 库主要由 C 语言编写,提供了底层的 API 接口。

1. 安装

在使用 zlib 之前,你需要安装其开发库。在基于 Debian/Ubuntu 的系统上,可以通过以下命令安装:

bash
sudo apt-get install zlib1g-dev

对于其他操作系统,请查阅相应的包管理器或从 zlib 官网 下载源代码编译安装。

2. 核心数据结构:z_stream

z_stream 结构体是 zlib 操作的核心。它维护着压缩或解压缩流的状态,并包含指向输入/输出缓冲区的指针、数据长度以及其他控制信息。

“`c
typedef struct z_stream_s {
z_const Bytef next_in; / 下一个输入字节 /
uInt avail_in; /
next_in 可用字节数 /
uLong total_in; /
目前为止的总输入字节数 */

Bytef    *next_out; /* 下一个输出字节应存放的位置 */
uInt     avail_out; /* next_out 剩余可用空间 */
uLong    total_out; /* 目前为止的总输出字节数 */

z_const char *msg;  /* 最后一条错误消息,无错误则为 NULL */
struct internal_state FAR *state; /* 应用程序不可见 */

alloc_func zalloc;  /* 用于分配内部状态 */
free_func  zfree;   /* 用于释放内部状态 */
voidp    opaque;  /* 传递给 zalloc/zfree 的私有数据对象 */

int      data_type; /* 数据类型猜测:二进制或文本 */
uLong    adler;   /* 未压缩数据的 adler32 或 crc32 值 */
uLong    reserved;  /* 保留供将来使用 */

} z_stream;
“`

3. 基本压缩示例

以下示例演示了如何使用 zlib 的流式 API 将数据从一个输入文件压缩到另一个输出文件。

“`c

include

include

include

include “zlib.h”

define CHUNK 16384 // 输入/输出缓冲区大小

/ 将源文件压缩到目标文件,直到源文件结束。
第四个参数是压缩级别:-1 表示默认,0 表示不压缩,
1 表示最快,9 表示最佳。
/
int def(FILE source, FILE dest, int level) {
int ret, flush;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

// 分配 deflate 状态
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, level); // 用压缩级别初始化压缩器
if (ret != Z_OK)
    return ret;

// 循环压缩直到文件结束
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; // 设置 flush 模式:文件结束时为 Z_FINISH
    strm.next_in = in;

    // 对输入数据运行 deflate(),直到输出缓冲区不再满
    do {
        strm.avail_out = CHUNK;
        strm.next_out = out;
        ret = deflate(&strm, flush); // 执行压缩
        assert(ret != Z_STREAM_ERROR);  // 状态未被破坏
        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);     // 所有输入都将被使用

} while (flush != Z_FINISH); // 继续直到所有输入都被处理并刷新
assert(ret == Z_STREAM_END);    // 流将完成

// 清理并返回
(void)deflateEnd(&strm); // 释放压缩器状态
return Z_OK;

}
“`

解释:

  1. deflateInit(&strm, level): 初始化 z_stream 结构体进行压缩。level 的取值范围是 0(不压缩)到 9(最佳压缩),-1 表示默认级别(通常是 6)。
  2. 输入循环 (do { ... } while (flush != Z_FINISH);): 以 CHUNK 大小的块从源文件读取数据。
  3. strm.avail_instrm.next_in: 这些字段告诉 zlib 有多少输入数据可用以及数据的位置。
  4. flush: 此参数控制 deflate() 如何处理数据。Z_NO_FLUSH 表示预期还有更多输入。Z_FINISH 表示所有输入已提供,并且 zlib 应该产生所有剩余的输出。
  5. 输出循环 (do { ... } while (strm.avail_out == 0);): 重复调用 deflate(),直到所有可用输入都被消耗或者输出缓冲区已满。
  6. strm.avail_outstrm.next_out: 这些字段指示输出缓冲区中可用空间的量以及写入压缩数据的位置。
  7. deflate(&strm, flush): 执行实际的压缩。它从 next_in 消耗数据并写入压缩数据到 next_out
  8. fwrite(): 将压缩数据从输出缓冲区写入目标文件。
  9. deflateEnd(&strm): 释放内部压缩状态,释放内存。

4. 基本解压缩示例

解压缩过程与压缩类似,使用 inflateInitinflateinflateEnd

“`c

include

include

include

include “zlib.h”

define CHUNK 16384

/ 将源文件解压缩到目标文件,直到源文件结束。 /
int inf(FILE source, FILE dest) {
int ret;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

// 分配 inflate 状态
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit(&strm); // 初始化解压缩器
if (ret != Z_OK)
    return ret;

// 循环解压缩直到 deflate 流结束或文件结束
do {
    strm.avail_in = fread(in, 1, CHUNK, source); // 读取压缩输入
    if (ferror(source)) {
        (void)inflateEnd(&strm);
        return Z_ERRNO;
    }
    if (strm.avail_in == 0)
        break;
    strm.next_in = in;

    // 对输入数据运行 inflate(),直到输出缓冲区不再满
    do {
        strm.avail_out = CHUNK;
        strm.next_out = out;
        ret = inflate(&strm, Z_NO_FLUSH); // 执行解压缩
        assert(ret != Z_STREAM_ERROR);  // 状态未被破坏
        switch (ret) {
        case Z_NEED_DICT:
            ret = Z_DATA_ERROR;     // 并继续
        case Z_DATA_ERROR:
        case Z_MEM_ERROR:
            (void)inflateEnd(&strm);
            return ret;
        }
        have = CHUNK - strm.avail_out;
        if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
            (void)inflateEnd(&strm);
            return Z_ERRNO;
        }
    } while (strm.avail_out == 0); // 如果输出缓冲区已满,则继续

} while (ret != Z_STREAM_END); // 继续直到压缩流结束

// 清理并返回
(void)inflateEnd(&strm); // 释放解压缩器状态
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;

}
“`

解释:

  1. inflateInit(&strm): 初始化 z_stream 结构体进行解压缩。
  2. 输入循环: 从源文件读取压缩数据。
  3. inflate(&strm, Z_NO_FLUSH): 执行解压缩。与 deflate() 不同,inflate() 通常将 Z_NO_FLUSH 作为 flush 参数,因为 zlib 格式是自终止的。
  4. 错误处理: switch 语句处理 inflate() 返回的各种错误码,例如 Z_DATA_ERROR 表示数据损坏或不完整,Z_MEM_ERROR 表示内存不足。
  5. inflateEnd(&strm): 释放内部解压缩状态。

5. 编译与运行

要编译使用 zlib 的 C 程序,你需要链接 zlib 库。对于 GCC 编译器,使用 -lz 标志:

bash
gcc your_program.c -o your_program -lz

进阶:压缩级别与错误处理

1. 压缩级别

zlib 提供 10 个压缩级别,从 0 到 9,默认级别为 6。这些级别代表了压缩比和压缩速度之间的权衡:

  • Z_NO_COMPRESSION (0): 不执行压缩,数据仅被存储。速度最快,但输出文件最大。
  • Z_BEST_SPEED (1): 压缩速度最快,但压缩比相对较低。
  • Z_DEFAULT_COMPRESSION (6): 在速度和压缩比之间取得良好平衡。
  • Z_BEST_COMPRESSION (9): 达到最高压缩比,但压缩速度最慢。

你可以在 deflateInit() 函数调用中指定压缩级别。

2. 错误处理

zlib 函数返回整数代码来指示成功或失败。检查这些返回码对于构建健壮的应用程序至关重要:

  • Z_OK: 操作成功。
  • Z_STREAM_END: 已达到压缩数据流的末尾(对于 inflate()),或所有输入已被处理且所有输出已生成(对于 deflate() 配合 Z_FINISH)。
  • Z_STREAM_ERROR: 无效的压缩/解压缩状态。
  • Z_DATA_ERROR: 输入数据损坏或不完整。
  • Z_MEM_ERROR: 内存不足。
  • Z_BUF_ERROR: 无法取得进展(例如,没有输入或没有输出缓冲区空间)。
  • Z_ERRNO: 发生了文件系统级别的错误(例如 freadfwrite 错误)。

z_stream.msg 字段有时可以提供更详细的错误消息。

3. 内存管理

默认情况下,zlib 使用 mallocfree 进行内部内存分配。但是,你可以通过在调用 deflateInit()inflateInit() 之前设置 z_stream 结构体中的 zalloczfreeopaque 字段来提供自定义的内存分配函数。

c
strm.zalloc = my_alloc_func;
strm.zfree = my_free_func;
strm.opaque = my_custom_data_pointer; // 传递给 my_alloc_func 和 my_free_func

高级:细粒度控制与Gzip支持

1. deflateInit2()inflateInit2()

为了对压缩和解压缩过程进行更高级的控制,zlib 提供了 deflateInit2()inflateInit2()。这些函数允许你指定以下参数:

  • windowBits: 控制历史缓冲区(LZ77 窗口大小)。更大的窗口大小可以提高压缩比,但需要更多内存。此参数还决定了输出格式(zlibgzip 或原始 DEFLATE)。
    • +8+15: zlib 头和校验和。15 是默认值。
    • -8-15: 原始 DEFLATE 流(无头/尾)。
    • +24+31 (即 16 + windowBits): gzip 头和校验和。
  • memLevel: 控制用于内部压缩状态的内存量。较高的值(1-9)通常会带来更好的压缩效果,但会占用更多内存。
  • strategy: 允许根据不同类型的数据调整压缩算法(例如,Z_FILTERED 用于过滤器产生的数据,Z_HUFFMAN_ONLY 仅用于霍夫曼编码)。

使用 deflateInit2() 输出 gzip 格式的示例:

c
ret = deflateInit2(&strm, level, Z_DEFLATED, 16 + MAX_WBITS, memLevel, Z_DEFAULT_STRATEGY);

2. 自定义字典

对于包含重复序列的数据流,尤其是在流的开头,使用自定义字典可以显著提高压缩效果。你可以在 deflateInit() 之后提供字典给 deflateSetDictionary(),并在 inflateInit() 之后提供给 inflateSetDictionary()。解压缩器必须使用与压缩器完全相同的字典。

3. 校验和

zlib 使用 Adler-32 校验和来检查未压缩数据的完整性。你可以在压缩或解压缩后通过 strm.adler 访问计算出的校验和。zlib 还提供了 adler32()crc32() 工具函数,用于手动计算这些校验和。

4. Gzip文件访问函数 (gz* functions)

为了直接处理 gzip 格式的文件,zlib 提供了一组模拟标准 C stdio 库的函数:

  • gzopen(): 打开一个 gzip 文件。
  • gzread(): 从 gzip 文件读取解压缩数据。
  • gzwrite(): 将压缩数据写入 gzip 文件。
  • gzclose(): 关闭一个 gzip 文件。
  • gzeof()gzerror() 等。

这些函数通过抽象 inflate/deflate 的流式调用,简化了 gzip 文件的处理。

使用 gzopen()gzread() 的示例:

“`c

include

include “zlib.h”

// … (错误处理和缓冲区定义)

gzFile inFileZ = gzopen(“example.gz”, “rb”);
if (inFileZ == NULL) {
// 处理错误
}

unsigned char buffer[CHUNK];
int unzippedBytes;
while ((unzippedBytes = gzread(inFileZ, buffer, CHUNK)) > 0) {
// 处理 buffer 中的解压缩数据
// 例如:fwrite(buffer, 1, unzippedBytes, stdout);
}
gzclose(inFileZ);
“`

Python zlib 模块

Python 提供了一个内置的 zlib 模块,为 zlib 库提供了便捷的接口。

1. 一次性压缩与解压缩

对于适合在内存中处理的小数据,你可以使用 zlib.compress()zlib.decompress()

“`python
import zlib

original_data = b”This is some data to be compressed.”

压缩数据

compressed_data = zlib.compress(original_data, level=zlib.Z_BEST_COMPRESSION)
print(f”原始大小: {len(original_data)} 字节”)
print(f”压缩后大小: {len(compressed_data)} 字节”)
print(f”压缩数据: {compressed_data}”)

解压缩数据

decompressed_data = zlib.decompress(compressed_data)
print(f”解压缩数据: {decompressed_data}”)

assert original_data == decompressed_data
“`

2. 流式压缩与解压缩

对于大型数据流,使用 zlib.compressobj()zlib.decompressobj() 可以分块处理数据。

“`python
import zlib

compressor = zlib.compressobj(level=zlib.Z_DEFAULT_COMPRESSION)
decompressor = zlib.decompressobj()

data_chunks = [
b”Part 1 of the data stream. “,
b”Part 2 of the data stream. “,
b”Part 3 of the data stream. ”
]

compressed_chunks = []
for chunk in data_chunks:
compressed_chunks.append(compressor.compress(chunk))
compressed_chunks.append(compressor.flush()) # 刷新任何剩余数据

full_compressed_data = b””.join(compressed_chunks)

decompressed_chunks = []
decompressed_chunks.append(decompressor.decompress(full_compressed_data))
decompressed_chunks.append(decompressor.flush()) # 刷新任何剩余数据

full_decompressed_data = b””.join(decompressed_chunks)

print(f”原始数据: {b”.join(data_chunks)}”)
print(f”解压缩数据: {full_decompressed_data}”)

assert b””.join(data_chunks) == full_decompressed_data
“`

总结

zlib 是一个功能强大且灵活的数据压缩库,为应用程序提供了高效的无损压缩能力。无论是通过 C/C++ 的底层 API 进行细粒度控制,还是利用 Python 模块进行便捷操作,理解其核心概念和不同接口的使用方式,都能帮助你更好地在项目中集成和应用数据压缩技术。从文件处理到网络通信,zlib 在各种场景下都发挥着不可替代的作用。


滚动至顶部