zlib 简介
zlib 是一个非常著名的数据压缩工具,而且是开源的,由 Jean-loup Gailly 与 Mark Adler 所开发。它能使用很少的系统资源,对各种数据都有很好的压缩效果,而且接口一直很稳定,被广泛用于各种项目中。
zlib 官网:https://zlib.net/
zlib 实例一:针对数据流的压缩
zlib 官方的 example 中提供了1个很好的数据压缩和解压缩的实例 zpipe.c ,代码很经典,但个人感觉使用起来有一些不太直观,以下是官方的 example code(本人添加了注释)
/* zpipe.c: zlib 库中压缩函数 deflate() 和解压缩函数 inflate() 的正确使用 */
#include <iostream>
#include <cstring>
#include <cstdio>
#include <assert.h>
#include "zlib.h"
// 避免行尾转换,只在Windows需要设置,不用管
#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
# include <fcntl.h>
# include <io.h>
# define SET_BINARY_MODE(file) _setmode(_fileno(file), O_BINARY)
#else
# define SET_BINARY_MODE(file)
#endif
#define CHUNK 16384
/**
* 从源文件中读取数据并压缩,写入到目标文件中,读取到 EOF 结束
* 返回:
* Z_OK: 成功
* Z_MEM_ERROR: 内存不足
* Z_STREAM_ERROR: 无效的压缩级别
* Z_VERSION_ERROR: 版本错误
* Z_ERRNO: 读写文件错误
*/
int def(FILE *source, FILE *dest, int level)
{
int ret, flush;
z_stream strm;
Byte in[CHUNK], out[CHUNK];
// 初始化压缩的参数
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// 初始化压缩stream
ret = deflateInit(&strm, level);
if (ret != Z_OK) return ret;
// 压缩,直至文件结束
do {
strm.avail_in = (uInt)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; // 输入数据起始地址,只操作指针,不拷贝数据
// 调用 deflate 压缩数据,直到所有输入数据被处理完
// strm.avail_out == 0 表示输出空间用完,但还有未压缩的数据,需要继续压缩
do {
strm.avail_out = CHUNK; // 输出空间剩余缓存大小
strm.next_out = out;
ret = deflate(&strm, flush);
assert(ret != Z_STREAM_ERROR);
int 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);
} while (flush != Z_FINISH);
assert(ret == Z_STREAM_END); // stream 已被压缩完成
// 清理 stream 并返回
(void)deflateEnd(&strm);
return Z_OK;
}
/**
* 从源文件中读取数据并解压缩,写入到目标文件中,读取到 EOF 结束
* 解压缩是还原数据,没有级别限制
* 返回:
* Z_OK: 成功
* Z_MEM_ERROR: 内存不足
* Z_STREAM_ERROR: 无效的压缩级别
* Z_VERSION_ERROR: 版本错误
* Z_ERRNO: 读写文件错误
*/
int inf(FILE *source, FILE *dest)
{
int ret;
z_stream strm;
Byte in[CHUNK], out[CHUNK];
// 初始化压缩的参数
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
// 初始化解压缩stream
ret = inflateInit(&strm);
if (ret != Z_OK) return ret;
// 解压缩,直至文件结束
do {
strm.avail_in = (uInt)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 解压缩数据,直到所有输入数据被处理完
// strm.avail_out == 0 表示输出空间用完,但还有未解压的数据,需要继续解压
do {
strm.avail_out = CHUNK; // 输出空间剩余缓存大小
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
int 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);
// 清理 stream 并返回,需要判断数据是否解压缩完,数据错误会导致无法解压缩
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
/* 打印错误信息 */
void zerrmsg(int ret)
{
std::cerr << "zpipe: ";
switch (ret) {
case Z_ERRNO:
if (ferror(stdin))
std::cerr << "error reading stdin";
if (ferror(stdout))
std::cerr << "error writing stdout";
break;
case Z_STREAM_ERROR:
std::cerr << "invalid compression level";
break;
case Z_DATA_ERROR:
std::cerr << "invalid or incomplete deflate data";
break;
case Z_MEM_ERROR:
std::cerr << "out of memory";
break;
case Z_VERSION_ERROR:
std::cerr << "zlib version mismatch!";
}
std::cerr << std::endl;
}
int main(int argc, char* argv[])
{
int ret;
// 避免行尾转换
SET_BINARY_MODE(stdin);
SET_BINARY_MODE(stdout);
// -d 表示解压缩
if (argc == 1) {
ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK) zerrmsg(ret);
return ret;
}
else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
ret = inf(stdin, stdout);
if (ret != Z_OK) zerrmsg(ret);
return ret;
}
else {
std::cerr << "zpipe usage: zpipe [-d] < source > dest" << std::endl;
return 1;
}
return 0;
}
在 VS 2015 上编译通过,可以先使用一个文本文件 100.txt 进行测试,文件这的内容如下,总共有 1000 个字符。
12345678901234567890123456789012345678901234567890
......
12345678901234567890123456789012345678901234567890
使用如下命令进行压缩和解压缩:
# 压缩
$ zpipe.exe < 100.txt > 100.txt.zlib
# 解压缩
$ zpipe.exe -d < 100.txt.zlib > 100.txt
这里需要解释以下,因为压缩函数 def 和解压缩函数 inf 的都是传入的标准输入输出(stdin 和 stdout),所以需要使用重定向才能读取和写入文件,这就是前面说的,使用上不太直观的地方,除此之外,压缩和解压缩的调用都很经典。
注意,z_stream 中的 avail_out 在函数调用过程中值会改变,用户需要根据这个值来判断压缩和解压缩是否完成,可以将它当作是一个输出参数。
压缩后的文件 100.txt.zlib 是一串二进制乱码,只有 34 个字节,可以看出,zlib对文本文件的压缩率还是很高的。
x?426153钒40$捙薊Q=F?-= L宋?
实例二:针对字节的压缩
若是只需要压缩一串简单的字节流,则可以直接调用 compress
和 uncompress
函数,方法如下:
#include <iostream>
#include <cstring>
#include "zlib.h"
// 压缩和解压缩测试
void test_compress_uncompress(const char* str, size_t len)
{
Byte compr[1024], uncompr[1024];
uLong compr_len, uncompr_len;
// 压缩
compr_len = sizeof(compr); // 字符串的结束符也要压缩
int err = compress(compr, &compr_len, (Byte*)str, (uLong)len + 1);
if (err != Z_OK) {
std::cerr << "compress error: " << err << std::endl;
exit(1);
}
std::cout << "orignal size: " << len + 1
<< ", compress size: " << compr_len << std::endl;
// 解压缩
uncompr_len = sizeof(uncompr);;
err = uncompress(uncompr, &uncompr_len, compr, compr_len);
if (err != Z_OK) {
std::cerr << "uncompress error: " << err << std::endl;
exit(1);
}
std::cout << "orignal size: " << compr_len
<< ", uncompress size: " << uncompr_len << std::endl;
// 判断解压缩后的数据和原始数据是否相同
if (std::strcmp(str, (const char*)uncompr)) {
std::cerr << "bad compress" << std::endl;
exit(1);
}
std::cout << "uncompress: " << uncompr << std::endl;
}
int main(int argc, char* argv[])
{
const char* str1 = "1234567890123456789012345678901234567890";
const char* str2 = "ajsdfljasfu928 asdfjaslkd sadfjaskljlasd";
test_compress_uncompress(str1, std::strlen(str1));
test_compress_uncompress(str2, std::strlen(str2));
return 0;
}
compress/uncompress
的函数定义如下:
/**
* 压缩函数,使用默认压缩等级 Z_DEFAULT_COMPRESSION
* 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存
*/
int compress(Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen);
/**
* 压缩函数2,可以手动指定压缩等级
* 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存;
* Z_STREAM_ERROR 压缩 level 参数非法
*/
int compress2(Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen,
int level);
/**
* 解压缩函数,需要预留足够的输出缓存(解压数据缓存)
* 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存;
* Z_DATA_ERROR 输入数据错误或不完整
*/
int uncompress(Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen);
/**
* 解压缩函数2,sourceLen 为指针,表示解压时消耗的源字节数,此时可能由于输出缓存不足,导致数据并没有被解压完
* 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存;
* Z_DATA_ERROR 输入数据错误或不完整
*/
int uncompress2(Bytef *dest, uLongf *destLen,
const Bytef *source, uLong *sourceLen);