zlib的原理与使用
Zlib是一个广泛使用的开源数据压缩库,稳定高效。它的核心是Deflate算法,是一种结合Lz77算法和哈夫曼编码的高效压缩算法。
Lz77算法
让我们想象这样一个字符串
1 | |
其中ABCDEFG重复了3次,一共23个字符,如果采用UTF-8编码,每个字母占据1字节,这个字符串共占据23字节。如果我们想要压缩这个字符串,利用字符串中的重复信息进行压缩无疑是最直观的想法。在Lz77算法中,我们可以用一组数字(offset,length)替换掉重复的信息,可以将上面的字符串表示为
1 | |
(9,7)就表示从当前位置的前面第9个字节开始的7个字节的内容。假设offset和length都用uint16_t表示,那我们就把字符串从23个字节压缩成了20个字节。
搜索缓冲区和前向缓冲区
为了最大化压缩率,理论上我们应该在整个历史数据中寻找最长的重复块。但这会导致搜索范围无限大,效率极低。
因此,LZ77 使用滑动窗口来平衡压缩效率和压缩率。这个窗口分为两部分:
- 搜索缓冲区 (Search Buffer):位于当前处理位置之前的数据,是寻找匹配项的“字典”。
- 前向缓冲区 (Lookahead Buffer):当前位置之后、等待被编码的数据。
zlib的编码器在滑动窗口的基础上采用贪心算法,也就是在搜索缓冲区中为前向缓冲区的内容寻找最长的匹配串。
例如
1 | |
如果我们直接固定搜索缓冲区和前向缓冲区的大小为24字节,并直接用前半段24字节的数据填充搜索缓冲区,那么压缩的结果是
1 | |
如果我们固定搜索缓冲区和前向缓冲区的大小为3字节
1 | |
显然前者的压缩效率更高,后者压缩效率更低。
重叠复制
前面我们提到
1 | |
这相当于编码器把“这个子串和2字节前那个长度为2的子串一样”这句话重复了15次,但实际上它只需要告诉我们“2字节前那个长度为2的子串又重复了15次”就行,也就是
1 | |
这就是重叠复制。这是一个很优雅的优化,甚至不用修改编码的格式,只要当我们解码的时候遇到length比offset要大的情况,我们就可以知道这里出现了重叠复制。
问题来了,如果原字符串是
1 | |
我们如果把它编码成
1 | |
那就解码错误了。我们只要把它编码成
1 | |
就行。这样压缩率也并不会损失多少。
最小匹配长度
上面的例子中,我们为什么不压缩成
1 | |
呢?这是因为Lz77存在最小匹配长度,这个长度取决于length和offset的类型。如果一个匹配串的长度小于存储 (offset, length) 对所需的字节数,那么这次替换就是得不偿失的。
在zlib的Deflate算法中,一个字面量(literal byte)会被当成一个符号,一个 (offset, length) 对会被当成两个符号。为了保证压缩有效,匹配的长度 length 必须至少为3。因为替换掉3个字节的原文,我们只需要存储一个长度符号和一个距离符号,这通常是划算的。任何长度为1或2的匹配都不会被编码成 (offset, length) 对,而是直接作为字面量输出。
具体实现流程
前面我们引入了滑动窗口,为了举例方便,假设过程是静态的。而在实际的Lz77流程中,窗口是持续滑动的。其大致流程如下:
- 初始化:将滑动窗口定位在输入数据的开始处。搜索缓冲区为空,前向缓冲区填充了数据的开头部分。
- 查找匹配:指针指向前向缓冲区的第一个字节。在搜索缓冲区中从后向前搜索,查找与前向缓冲区开头部分最长的匹配串。
- 生成输出:
- 如果找到匹配:且匹配长度大于等于最小匹配长度(在zlib中是3),则输出一个
(offset, length)对。然后,将滑动窗口整体向前移动length个字节。这是因为在找到匹配(offset, length)时,我们一次性“消费”了前向缓冲区里这length个字节,前移length个字节后搜索缓冲区内仍是已经完成编码的数据。 - 如果未找到匹配:或者最长匹配长度小于最小匹配长度,则输出前向缓冲区的第一个字节作为“字面量”(Literal)。然后,将滑动窗口向前移动
1个字节。
- 如果找到匹配:且匹配长度大于等于最小匹配长度(在zlib中是3),则输出一个
- 重复:持续执行第2步和第3步,直到前向缓冲区中没有数据为止。
举个例子,处理abracadabra的步骤如下
| 步骤 | 状态 | 搜索缓冲区 | 前向缓冲区 | 操作描述 | 匹配结果 | 输出 | 窗口移动 |
|---|---|---|---|---|---|---|---|
| 1 | | abracadabra |
(空) | abracadabra | 搜索缓冲区为空,无法匹配任何内容 → 输出字面量 | 未找到 | Literal(‘a’) | 前进 1 字节 |
| 2 | a | bracadabra |
a | bracadabra | 尝试匹配 'b',在搜索缓冲区中没有出现 |
未找到 | Literal(‘b’) | 前进 1 字节 |
| 3 | ab | racadabra |
ab | racadabra | 尝试匹配 'r',搜索缓冲区中没有 |
未找到 | Literal(‘r’) | 前进 1 字节 |
| 4 | abr | acadabra |
abr | acadabra | 从 'a' 开始:匹配到 'a',但继续扩展 'ac' 时失败 → 匹配长度=1 < 3 |
匹配太短(无效) | Literal(‘a’) | 前进 1 字节 |
| 5 | abra | cadabra |
abra | cadabra | 尝试匹配 'c',搜索缓冲区中没有 |
未找到 | Literal(‘c’) | 前进 1 字节 |
| 6 | abrac | adabra |
abrac | adabra | 从 'a' 开始:匹配到 'a',但扩展 'ad' 时失败 → 匹配长度=1 < 3 |
匹配太短(无效) | Literal(‘a’) | 前进 1 字节 |
| 7 | abraca | dabra |
abraca | dabra | 尝试匹配 'd',搜索缓冲区中没有 |
未找到 | Literal(‘d’) | 前进 1 字节 |
| 8 | abracad | abra |
abracad | abra | 从 'a' 开始:匹配 'a' → 'ab' → 'abr' → 'abra',继续 'abrac' 失败 → 最长匹配长度=4 |
找到,长度=4 ≥ 3 | (offset=7,len=4) | 前进 4 字节 |
最后输出abracad(7,4)。
这套流程也能自然而然地实现重叠复制。
处理第一个字符
'a'- 状态:
| ababababab - 搜索缓冲区: (空)
- 前向缓冲区:
abababababc - 操作: 搜索缓冲区为空,没有匹配。
- 结果: 未找到匹配。
- 输出:
Literal('a') - 窗口移动: 向前移动 1 个字节。
- 状态:
处理第二个字符
'b'- 状态:
a | bababababc - 搜索缓冲区:
a - 前向缓冲区:
babababab - 操作: 在搜索缓冲区中查找
'b',未找到匹配。 - 结果: 未找到匹配。
- 输出:
Literal('b') - 窗口移动: 向前移动 1 个字节。
- 状态:
处理后续子串
'ababababc'状态:
ab | ababababc搜索缓冲区:
ab前向缓冲区:
ababababc操作:
- 前向缓冲区第1个字符
'a'在 offset=2 找到匹配。 - 第2个字符
'b'继续匹配成功。 - 搜索缓冲区只有 2 字节,但是搜索指针并不受搜索缓冲区的限制,它会继续向前进入前向缓冲区,直到指向
'c'后终止匹配 - 依次匹配
'ababababc',最终匹配长度=8。
- 前向缓冲区第1个字符
结果: 找到匹配
(offset=2, length=8)。输出:
(2, 8)窗口移动: 一次性前移 8 个字节
处理最后一个字符
'c'- 状态:
ababababab | c(这是执行完上一步,窗口前移8个字节后的新状态) - 搜索缓冲区:
ababababab - 前向缓冲区:
c - 操作: 在搜索缓冲区中查找
'c'。 - 结果: 未找到匹配。
- 输出:
Literal('c') - 窗口移动: 向前移动 1 个字节,整个字符串处理完毕。
- 状态:
最终输出ab(2,8)c
这个过程的输出不是最终的压缩数据,而是一个由“字面量”和 (offset, length) 对组成的中间序列,这个序列将交给下一步的哈夫曼编码进行处理。
哈夫曼树
LZ77算法通过消除重复数据,极大地减少了数据量。但它的输出(字面量和指针)仍然是未经压缩的。哈夫曼编码的作用就是对LZ77的输出进行第二轮压缩,让最终的数据变得更小。
它的核心思想是:为出现频率高的符号分配更短的编码,为出现频率低的符号分配更长的编码。
例如,在一段文本中,字母 e 可能出现最频繁,而字母 z 可能很少出现。那么,哈夫曼编码可能会用 10 来表示 e,而用 11010011 来表示 z。
Deflate算法中的哈夫曼编码有几个特点:
处理两种符号:它需要处理两种类型的符号:
- 字面量/长度 (Literal/Length):0-255的ASCII码值是字面量。此外,还有一些特殊值代表匹配的长度(例如,长度为3、长度为4等)。
- 距离 (Distance):代表LZ77中的
offset。
两棵哈夫曼树:Deflate标准为这两种符号分别构建了两棵独立的哈夫曼树。一棵用于压缩“字面量/长度”符号,另一棵用于压缩“距离”符号。
动态构建:zlib可以分析输入数据块,统计每种符号的出现频率,然后为该数据块动态生成最优的哈夫曼树。这使得压缩非常自适应。当然,zlib也提供了使用预定义的“静态哈夫曼树”的选项,这在处理小数据块时可以节省编码树本身占用的空间。
结合过程:
LZ77处理后的数据流 A, B, (10, 5), C, ... 会被转换成一个符号流:Literal(A), Literal(B), Length(5), Distance(10), Literal(C), ...。然后,哈夫曼编码器会用对应哈夫曼树中的可变长码字来替换这些符号,生成最终的二进制压缩流。
常用接口
zlib 提供了多个层次的接口,包括简单的一体化函数和底层的流式接口。
一体化接口 (compress / uncompress)
这是最简单、最直接的接口,适用于待处理的数据可以一次性完全载入内存的场景。
compress(dest, destLen, source, sourceLen)
功能: 将
source指向的内存块中的数据进行压缩,并将结果写入dest指向的内存块。此函数使用默认的压缩级别 (Z_DEFAULT_COMPRESSION)。参数含义:
Bytef *dest: 指向用于存储压缩后数据的目标内存缓冲区的指针。uLongf *destLen: 一个指向uLong类型变量的指针。- 输入时:
*destLen的值必须是dest缓冲区的大小(以字节为单位)。 - 输出时: 如果函数成功,
*destLen的值会被更新为实际压缩后数据的大小。
- 输入时:
const Bytef *source: 指向源数据(待压缩数据)的指针。uLong sourceLen: 源数据的长度(以字节为单位)。
内部工作流程:
- 初始化: 在函数内部,它会临时创建一个
z_stream结构体,并使用默认参数进行初始化。 - 设置流参数: 将
source和dest的地址和长度赋值给内部的z_stream结构体。 - 执行压缩: 调用核心的
deflate引擎,一次性完成整个压缩过程。 - 清理: 释放所有临时分配的资源。
- 返回: 通过更新
*destLen和返回状态码来告知调用者结果。如果dest缓冲区不够大,会返回Z_BUF_ERROR。
- 初始化: 在函数内部,它会临时创建一个
uncompress(dest, destLen, source, sourceLen)
功能: 将
source指向的已经压缩的数据块解压到dest指向的内存块中。参数含义:
Bytef *dest: 指向用于存储解压后数据的目标内存缓冲区的指针。uLongf *destLen: 一个指向uLong类型变量的指针。- 输入时:
*destLen的值必须是dest缓冲区的大小。你必须提前分配足够大的空间来存放原始数据。 - 输出时: 如果函数成功,
*destLen的值会被更新为实际解压后数据的大小。
- 输入时:
const Bytef *source: 指向待解压数据的指针。uLong sourceLen: 待解压数据的长度。
内部工作流程:
- 初始化: 与
compress类似,函数内部会临时创建一个z_stream结构体并初始化。 - 设置流参数: 将输入和输出缓冲区的相关信息设置到内部的
z_stream中。 - 执行解压: 调用核心的
inflate引擎,一次性完成整个解压过程。 - 清理: 释放所有内部资源。
- 返回: 更新
*destLen并返回状态码。如果dest缓冲区不足,会返回Z_BUF_ERROR;如果输入数据损坏,会返回Z_DATA_ERROR。
- 初始化: 与
流式接口 (deflate / inflate)
这是 zlib 更灵活、更强大的核心接口。它适用于处理大文件、网络数据流等无法一次性加载到内存的数据。这种方式允许你分块送入数据,并分块获取处理结果,极大地降低了内存消耗。
z_stream 结构体
流式接口的核心是 z_stream 结构体,它像一个“会话”或“上下文”,记录了压缩/解压过程中的所有状态信息。我们需要在整个流式处理的生命周期中维护这个结构体。
z_stream结构体的主要成员:const Bytef *next_in: (输入) 指向下一块待处理的输入数据。uInt avail_in: (输入)next_in指针指向的缓冲区中,可用的输入数据字节数。uLong total_in: (只读) 从流开始到现在,总共读取的输入字节数。Bytef *next_out: (输出) 指向用于存放处理结果的输出缓冲区的下一个可用位置。uInt avail_out: (输出)next_out指针指向的缓冲区中,剩余的可用空间字节数。uLong total_out: (只读) 从流开始到现在,总共生成的输出字节数。char *msg: 当发生错误时,这里可能会包含一个描述错误的字符串。voidpf opaque: 一个数据指针,zlib 不会使用它,但可以由应用程序用来传递自定义数据。通常设为Z_NULL。alloc_func zalloc: 用于动态内存分配的函数指针。通常设为Z_NULL,让 zlib 使用标准库的malloc。free_func zfree: 用于释放内存的函数指针。通常设为Z_NULL,让 zlib 使用标准库的free。
压缩流程
deflateInit(z_streamp strm, int level):- 功能: 初始化一个
z_stream结构体用于压缩。这个函数必须在调用任何deflate之前被调用。 - 参数含义:
z_streamp strm: 指向z_stream结构体的指针。int level: 压缩级别,取值范围:Z_NO_COMPRESSION(0): 不压缩Z_BEST_SPEED(1): 最快速度Z_BEST_COMPRESSION(9): 最高压缩率Z_DEFAULT_COMPRESSION(-1): 默认级别,在速度和压缩率之间取得平衡。
- 内部工作流程:
- 为内部压缩状态分配内存(例如哈希表、滑动窗口等)。
- 根据指定的
level设置压缩算法参数。 - 初始化
strm结构体中的各个字段。
- 功能: 初始化一个
deflate(z_streamp strm, int flush):- 功能: 核心的压缩函数。它会从
strm->next_in读取数据,进行压缩,然后将结果写入strm->next_out。这个函数通常在一个循环中被反复调用,直到所有输入数据都被处理完毕。 - 参数含义:
z_streamp strm: 指向已初始化的z_stream结构体的指针。int flush: 一个非常重要的参数,用于控制压缩引擎的行为和输出缓冲区的刷新策略。常用值包括:Z_NO_FLUSH: 正常处理,允许 zlib 在内部积累数据以获得更好的压缩率。Z_SYNC_FLUSH: 刷新所有待处理的输出。所有输出都会被写入next_out。这对于网络数据包之类的应用很有用。Z_FINISH: 表明这是最后一批输入数据。它会处理完所有剩余的输入,并输出压缩流的结束标记。调用此flush模式后,必须持续调用deflate直到它返回Z_STREAM_END。
- 内部工作流程:
- 检查
strm->avail_in,如果有输入数据,则进行压缩。 - 使用 LZ77 算法查找重复字符串,并使用霍夫曼编码对结果进行编码。
- 将压缩后的数据写入
strm->next_out,并更新strm->avail_out和strm->next_out。 - 更新
strm->avail_in,strm->next_in,strm->total_in,strm->total_out。 - 根据
flush参数的指令,执行相应的刷新操作。
- 检查
- 功能: 核心的压缩函数。它会从
deflateEnd(z_streamp strm):- 功能: 清理和释放由
deflateInit分配的所有内部状态和内存。 - 参数含义:
z_streamp strm: 指向需要被清理的z_stream结构体的指针。
- 内部工作流程:
- 释放所有与该压缩流相关的动态分配的内存。
- 将
strm的内部状态重置,以防止误用。
- 功能: 清理和释放由
解压流程
解压流程与压缩流程在结构上非常相似。
inflateInit(z_streamp strm):- 功能: 初始化一个
z_stream结构体用于解压。 - 参数含义:
z_streamp strm: 指向你所分配的z_stream结构体的指针。
- 内部工作流程:
- 为内部解压状态分配所需的内存(如滑动窗口、霍夫曼树等)。
- 初始化
strm结构体。
- 功能: 初始化一个
inflate(z_streamp strm, int flush):- 功能: 核心的解压函数。它从
strm->next_in读取已压缩的数据,进行解压,然后将结果写入strm->next_out。同样,它也通常在循环中被调用。 - 参数含义:
z_streamp strm: 指向已初始化的z_stream结构体的指针。int flush: 对于解压,flush参数通常使用Z_NO_FLUSH。Z_SYNC_FLUSH等值也有特定用途,但不如压缩时常用。
- 内部工作流程:
- 从输入流中读取数据,解析霍夫曼编码。
- 重建原始数据,处理 LZ77 算法中的匹配(”距离-长度”对)。
- 将解压后的数据写入
strm->next_out,并更新相应的计数器和指针。 - 当解压到数据流的末尾时,它会返回
Z_STREAM_END。
- 功能: 核心的解压函数。它从
inflateEnd(z_streamp strm):- 功能: 清理和释放由
inflateInit分配的所有资源。 - 参数含义:
z_streamp strm: 指向需要被清理的z_stream结构体的指针。
- 内部工作流程:
- 释放所有与该解压流相关的动态分配的内存。
- 重置
strm结构体。
- 功能: 清理和释放由
源码解析
compress2
1 | |
compress2是zlib库中最主要的实现函数,它处理一个完整的、已知的内存块,将其压缩后放入另一个内存块中,用 zlib 提供的更底层的流式接口(deflateInit, deflate, deflateEnd)封装了整个压缩流程。
和compress的区别在于多出了level参数,compress其实就是调用了level = Z_DEFAULT_COMPRESSION也就是使用默认压缩级别的compress2。
首先来看函数签名
1 | |
int ZEXPORT:int: 函数的返回值。它是一个状态码,用于表示压缩是否成功。Z_OK(值为0) 代表成功,其他负数值代表不同类型的错误(如内存不足Z_MEM_ERROR、缓冲区太小Z_BUF_ERROR等)。ZEXPORT: 这是一个宏,用于在构建动态链接库(DLL)或共享库(.so)时,将此函数导出,使其能被其他程序调用。
Bytef *dest- 作用: 指向目标缓冲区的指针。压缩后的数据将被写入这个缓冲区。
- 类型:
Bytef是 zlib 中对unsigned char的别名,代表一个字节。
uLongf *destLen- 输入时:
*destLen的值必须是dest指向的缓冲区的大小(以字节为单位)。函数需要知道目标缓冲区有多大,以防止写入时发生溢出。 - 输出时: 如果压缩成功,
*destLen的值会被函数修改为实际压缩后数据的大小(以字节为单位)。 - 类型:
uLongf是 zlib 中对unsigned long的别名。
- 输入时:
const Bytef *source- 指向源数据缓冲区的指针,也就是需要被压缩的数据。
const关键字: 表示这个函数不会修改源数据的内容。
uLong sourceLen- 作用: 源数据的长度(以字节为单位)。
int level- 指定压缩级别。这是一个在压缩速度和压缩率之间进行权衡的参数。
- 取值范围:
0: 不进行压缩。1到9: 压缩级别从低到高。1(Z_BEST_SPEED) 速度最快,但压缩率最低;9(Z_BEST_COMPRESSION) 压缩率最高,但速度最慢。-1:Z_DEFAULT_COMPRESSION,这是一个在速度和压缩率之间取得良好平衡的默认值(通常是 6)。
代码的核心是围绕一个 z_stream 结构体来组织的,它保存了整个压缩过程的状态。
初始化
z_stream1
2
3
4
5
6z_stream stream;
int err;
// ...
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = (voidpf)0;z_stream stream;: 定义一个核心的状态结构体。stream.zalloc,stream.zfree,stream.opaque: 这三个字段用于自定义内存分配。这里将它们都设置为0或NULL,是告诉 zlib 使用其内部默认的malloc/free函数来管理内存。
初始化压缩器状态
1
2err = deflateInit(&stream, level);
if (err != Z_OK) return err;deflateInit(&stream, level): 这是 zlib 中用于初始化压缩流的函数。- 它会为
stream结构体分配内部状态所需的所有内存(例如,内部缓冲区、哈希表等)。 level参数被传入,以决定压缩策略。- 如果内存分配失败或
level参数无效,它会返回一个错误码。
- 它会为
设置输入和输出缓冲区
1
2
3
4stream.next_out = dest;
stream.avail_out = 0; // 初始设为0,循环内会设置
stream.next_in = (z_const Bytef *)source;
stream.avail_in = 0; // 初始设为0,循环内会设置stream.next_in/stream.next_out: 分别指向下一次要读取的输入字节和下一次要写入的输出字节的位置。stream.avail_in/stream.avail_out: 分别表示输入缓冲区中还有多少可用字节和输出缓冲区中还有多少剩余空间。- 注意: 这里将
avail_in和avail_out初始化为 0,是因为实际的赋值操作在do-while循环内部进行,以处理可能超过uInt最大值的大块数据。
核心压缩循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14do {
// A. 如果输出缓冲区满了,就重新填充 avail_out
if (stream.avail_out == 0) {
stream.avail_out = left > (uLong)max ? max : (uInt)left;
left -= stream.avail_out;
}
// B. 如果输入数据消耗完了,就重新填充 avail_in
if (stream.avail_in == 0) {
stream.avail_in = sourceLen > (uLong)max ? max : (uInt)sourceLen;
sourceLen -= stream.avail_in;
}
// C. 调用核心压缩引擎
err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH);
} while (err == Z_OK);这是整个函数最核心的部分。zlib 的
deflate函数是分块处理数据的。- A 和 B (分块处理逻辑):
stream.avail_in和stream.avail_out的类型是uInt(通常是 32 位无符号整数)。如果原始数据长度sourceLen或目标缓冲区长度destLen超过了uInt的最大值(在 64 位系统上,uLong可以远大于uInt),就需要分块送入deflate函数。这里的代码确保每次送入的数据块大小不超过uInt的最大值max。 - C (
deflate函数调用):deflate(&stream, flush_mode): 这是执行压缩的引擎函数。它会从stream.next_in读取数据,将压缩结果写入stream.next_out,并相应地更新avail_in,avail_out,next_in,next_out的值。flush_mode参数是关键:sourceLen ? Z_NO_FLUSH : Z_FINISH: 这是一个三元运算符。Z_NO_FLUSH: 如果还有源数据没有提供给deflate(sourceLen > 0),就使用这个模式。它告诉deflate尽可能多地压缩数据,但不要结束整个压缩流,因为后面还有数据。Z_FINISH: 当所有源数据都已经被deflate读取 (sourceLen == 0),就使用这个模式。它告诉deflate处理所有剩余的缓冲数据,并写入压缩流的结束标记。这是完成压缩的最后一步。
- 循环条件 (
while (err == Z_OK)):- 只要
deflate返回Z_OK,就意味着压缩仍在进行中,但尚未完成(通常是因为输出缓冲区满了)。循环会继续,为deflate提供新的空间。 - 当
deflate返回Z_STREAM_END时,表示压缩已成功完成,循环会终止。 - 如果
deflate返回任何其他错误代码,循环也会终止。
- 只要
- A 和 B (分块处理逻辑):
清理和返回
1
2
3*destLen = stream.total_out;
deflateEnd(&stream);
return err == Z_STREAM_END ? Z_OK : err;*destLen = stream.total_out;:stream.total_out记录了整个压缩过程中总共输出的字节数。函数在结束前,用这个最终值更新调用者传入的destLen。deflateEnd(&stream);: 这是deflateInit的配对函数。它会释放deflateInit分配的所有内部状态和内存,防止内存泄漏。return err == Z_STREAM_END ? Z_OK : err;:- 如果
deflate最终返回Z_STREAM_END,说明压缩流已成功结束,整个compress2函数就向调用者返回Z_OK,表示一切顺利。 - 如果循环因为其他错误(比如输出缓冲区不够大,
deflate返回Z_BUF_ERROR)而终止,则将这个原始的错误码返回给调用者。
- 如果
uncompress
uncompress 函数的内部结构与 compress2 非常相似,同样是基于流式接口的封装。让我们简单看一下它的实现,以了解解压过程。
1 | |
与compress2的对比:
- 核心函数不同:使用
inflateInit,inflate,inflateEnd这一套解压接口。 - 无需循环:
uncompress假设输入和输出缓冲区都足够大,可以一次性完成所有操作。因此,它只调用一次inflate函数,而不是像compress2那样使用do-while循环。 - Flush模式:调用
inflate时,直接使用Z_FINISH。这告诉inflate引擎,提供的输入数据就是完整的压缩块,解压到数据流末尾就应该停止。 - 错误处理:解压过程可能遇到更多数据本身的问题(如数据损坏
Z_DATA_ERROR),因此错误处理逻辑更复杂一些。