官方手册 :
http://www.zlib.net/manual.html
参考连接 :
http://blog.csdn.net/zhoudaxia/article/details/8039519
http://www.oschina.net/code/snippet_65636_22542
http://blog.163.com/bh_binghu/blog/static/94553512011626102054571/
请多多支持以上作者…
另外,推荐下载源码同步学习.
研究了好久的zlib,今天终于可以拿得出手了.这里分享一下使用的经验.
基础数据结构
z_stream : 压缩算法,压缩程度以及输入输出buffer和长度等都保存在这里,可以理解为压缩上下文.
常用的函数
deflateInit : 参数比较少,里面的实现其实是调用的deflateInit2
deflateInit2 : 压缩初始化的基础函数,有很多参数,下面会重点介绍
deflate : 压缩函数.
deflateEnd : 压缩完成以后,释放空间,但是注意,仅仅是释放deflateInit中申请的空间,自己申请的空间还是需要自己释放.
inflateInit : 解压初始化函数,内部调用的inflateInit2.
inflateInit2 : 解压初始化的基础函数.后面重点介绍.
infalte : 解压函数
inflateEnd : 同deflateEnd作用类似.
compress : 全部附加选项默认压缩,内部调用compress2
compress2 : 带level的压缩方式.
uncompress : 解压缩.
压缩函数介绍
deflateInit2 :
函数原型为 : int ZEXPORT deflateInit2(z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy)
z_stream :
这个是压缩上下文,你在调用deflateInit2之前首先要做的就是初始化这个结构体,很简单,个里面的三个回调赋值就行了.连官方给出的例子也是置成NULL.所以这里直接这样初始化,strm.zalloc = NULL; strm.zfree = NULL; strm.opaque = NULL.
level :
这个参数目前可选值有四个(好吧,在我看来,有两个没啥用嘛),如下:
Z_NO_COMPRESSION : 不压缩
Z_BEST_SPEED : 速度优先,可以理解为最低限度的压缩.
Z_BEST_COMPRESSION : 压缩优先,但是速度会有些慢.
Z_DEFAULT_COMPRESSION : 没仔细看源码.compress里面用的就是这个选项.
method
: 就压缩嘛,所以选项就只有Z_DEFLATED
windowBits
: 这个选项可就有点复杂了,也很重要,取值与压缩方式如下:
* -(15 ~ 8) : 纯deflate压缩
* 15 ~ 8 : 带zlib头和尾
* > 16 : 带gzip头和尾
所以,无论是zlib的压缩还是gzip的压缩,再或者是纯deflate流,zlib都能搞定.验证方式 :
1. zlib的话可以使用命令 cat 压缩文件名 | zlib-deflate -uncompress验证(家里的电脑没有这个命令,忘记什么包里面的了.)
2. 如果是纯deflate的压缩数据,只能字节写解压的代码.注意调用inflateInit2的时候这个值要对应.
3. 如果是gzip的方式,就直接用gunzip验证.
memLevel
: 目前只有一个选项,MAX_MEM_LEVEL,无非是运行过程中对内存使用的限制.
strategy
: 直接给默认就行Z_DEFAULT_STRATEGY.
deflate :
函数原型 : ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
strm : 这个参数就是上面调用了deflateInit2初始化过后的变量.调用deflate之前,需要为它指定四个成员.
strm.next_in = 你的待压缩数据
strm.next_out = 压缩以后数据存储的buffer
strm.avail_in = 待压缩数据的长度
strm.avail_out = 压缩数据存储buffer的长度.
这个next_out 没有固定的长度,并不是说因为是压缩,压缩以后的数据一定比原来的短,所以就可以少给,一般建议起码给到next_in的长度.
flush : 没有用过别的选项,
Z_NO_FLUSH : 如果是一个大文件,没有办法一次加载进内存,那么这个选项就派上用场了.它告诉zlib我还会再给你数据的.
这个参数对应的deflate的返回值是Z_OK.
Z_FINISH : 这个标记才是告诉zlib,我没有数据了,这次是最后一个.
这个参数对应的deflate的返回值是Z_STREAM_END
deflateEnd
:
上面的描述已经足够清楚,它没有更多的作用了.
compress2
:
函数原型 : ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level));
如果你看了它的内部实现,就会知道,这其实就是上面三个函数的封装.供使用者偷懒用的,谁不喜欢一步到位呢.不过要明确它的缺点 :
a) 它的确很方便,但是却限制了所有的可能性,因为它只能压缩一次,也就是说内部直接调用的Z_FINISH
b) 不要觉得保存的是压缩的数据就给小buffer,有一篇博客给出的值是输入buffer的四倍.这个亏我是吃过的,前几天用这个函数,给了输入buffer的大小,我觉得足够了.但是始终返回Z_BUF_ERROR.后来看到那篇博客才恍然大悟.
解压函数介绍
有压缩自然也得有解压,并且都是一一对应的.
inflateInit2
: 初始化
函数原型 : inflateInit2(z_streamp strm, int windowBits)
strm
: 和deflate一样,初始化三个回调以后即可.有的参考文档说还需要初始化第四个选项,具体记不清哪个了.不过我试过以后发现貌似不用.
windownBits
: 含义和deflateInit2一样,而且一定要对应起来.
inflate
: 解压
函数原型 : ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
strm
: 四个参数.
strm.next_in = 你的待解压数据
strm.next_out = 解压以后数据存储的buffer
strm.avail_in = 待解压数据的长度
strm.avail_out = 解压数据存储buffer的长度.
flush
: 和deflate一样,如果是Z_NO_FLUSH说明还有数据没有解压,如果是Z_FINISH说明这是最后一包待解压数据.
inflateEnd
: 释放上面两步骤里面申请的资源.
uncompress
:
函数原型 : Z
EXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen));
dest : 解压以后的数据存在这里
destLen : dest 大小
source : 待解压数据buffer
sourceLen : 待解压数据长度
其实这个函数就是简单封装了inflateInit, inflate, inflateEnd.同样,这个函数只适合单独解压场景,不适合需要多次传入的场景.
关于返回值
现在看来,除了deflate和inflate成功会有两个返回值以外,其他的函数返回值成功的话都是Z_OK.
如果 flush == Z_NO_FLUSH, 这两个函数返回值是Z_OK
如果flush == Z_FINISH, 这两个函数的返回值是Z_STREAM_END
一定要区分.
一个简单的例子
这个例子太像官方提供的例子了.不过还是贴出来,加了几句注释,希望能够帮助理解.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <zlib.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#define READ_LEN 8192
// gzip头
#define GZIP_MAGIC "\037\213" /* Magic header for gzip files, 1F 8B */
// 目前只有这种压缩方式
#define DEFLATED 8
// 标记压缩为快速还是高质量
#define FAST 4
#define SLOW 2
// gzip格式
// (u16)MAGIC + (u8)DEFLATE + (u8)flag + (u32)time + (u8)deflate_flag + (u8)OS_CODE
int main(int argc, char **argv)
{
if (argc < 3)
{
printf("usage : %s src_file dst_file\n", argv[1]);
return -1;
}
int ret = 0;
int rfd = 0;
uint64_t read_len = 0;
char *read_buf = NULL;
int wfd = 0;
uint64_t write_len = 0;
char *write_buf = NULL;
rfd = open(argv[1], O_RDONLY);
if (-1 == rfd)
{
perror("\n");
return -1;
}
wfd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0666);
if (wfd < 0)
{
perror("\n");
return -1;
}
read_buf = (char *)malloc(READ_LEN);
if (NULL == read_buf)
{
return -1;
}
write_len = READ_LEN * 4;
write_buf = (char *)malloc(write_len);
if (NULL == write_buf)
{
return -1;
}
uint8_t *ptr = (uint8_t*)write_buf;
z_stream stream;
stream.zalloc = NULL;
stream.zfree = NULL;
stream.opaque = NULL;
deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY);
AGAIN:
while ((read_len = read(rfd, read_buf, READ_LEN)) >= 0)
{
int flush = Z_NO_FLUSH;
if (read_len == 0)
{
flush = Z_FINISH;
}
stream.next_in = (uint8_t*)read_buf;
stream.avail_in = read_len;
do
{
write_len = 4 * READ_LEN;
stream.next_out = (uint8_t*)write_buf;
stream.avail_out = write_len;
ret = deflate(&stream, flush);
if (flush == Z_NO_FLUSH && ret != Z_OK)
{
goto ERROR;
}
if (flush == Z_FINISH && ret != Z_STREAM_END)
{
goto ERROR;
}
int avail = 4 * READ_LEN - stream.avail_out;
write(wfd, write_buf, avail);
}while (stream.avail_out == 0); // 如果avail_out不够用了,说明输入的buffer还有没有压缩的部分,那么要继续压缩.
// 如果输入长度不是0,说明压缩不完整,可以直接退出.
assert(stream.avail_in == 0);
if (read_len == 0)
{
break;
}
}
if (read_len < 0)
{
if (errno == EINTR)
{
goto AGAIN;
}
}
return 0;
ERROR:
deflateEnd(&stream);
close(rfd);
close(wfd);
return -1;
}
这段代码,我使用的是gzip的压缩方式,所以如果要解压,不需要在单独写一份代码了.只要gunzip命令就可以验证程序是否正常.
后记 :
关于三种压缩格式的区分.
deflate : 说他是压缩格式有点不确切,其实它就是压缩的核心算法.
zlib : zlib header + deflate + zlib tailer
gzip : gzip header + deflate + gzip tailer
另外我封装了一套单buffer的压缩和解压函数.也放在这里,有需要的同学可以参考一下.
compress.h
#ifndef MYZIP_H
#include <unistd.h>
#include <stdint.h>
#include <zlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef z_streamp PZSTREAM;
typedef z_stream ZSTREAM;
/* ===========================================================================
* 指定压缩(解压)类型
* 官方文档提到 :
* -(15 ~ 8) : raw deflate压缩
* 15 ~ 8 : 带zlib头和尾
* > 16 : 带gzip头和尾
*/
typedef enum
{
E_ZIP_RAW = -MAX_WBITS,
E_ZIP_ZLIB = MAX_WBITS,
E_ZIP_GZIP = MAX_WBITS + 16
} zip_type;
// 压缩回调函数
typedef void (*flate_cb)(void *buf, int len);
/******************************************************************************
** 执行deflate压缩
** NOTE : 1. 这组函数仅仅适用于小buffer
2. 在调用deflate_beg之前,需要自己初始化strm
3. 如果deflating失败,要执行deflate_end释放资源
4. 明确一点,deflate_end只做了stream内部资源释放工作,如果stream是动态分配的,需要自己free
******************************************************************************/
int deflate_beg(PZSTREAM strm, zip_type type);
int deflating(PZSTREAM strm, uint8_t *inbuf, uint32_t in_len, uint8_t *outbuf, uint32_t *out_len);
int deflate_end(PZSTREAM strm);
/******************************************************************************
** 执行inflate解压
** NOTE : 1. 这组函数仅仅适用于小buffer
2. 在调用inflate_beg之前,需要自己初始化strm
3. 如果inflating失败,要执行inflate_end释放资源
4. 明确一点,inflate_end只做了stream内部资源释放工作,如果stream是动态分配的,需要自己free
******************************************************************************/
int inflate_beg(PZSTREAM strm, zip_type type);
int inflating(PZSTREAM strm, uint8_t *inbuf, uint32_t in_len, uint8_t *outbuf, uint32_t *outlen);
int inflate_end(PZSTREAM strm);
#ifdef __cplusplus
}
#endif
#endif
compress.c
#include <unistd.h>
#include "compress.h"
/// strm需要在传入之前自己初始化
int deflate_beg(PZSTREAM strm, zip_type type)
{
int ret = 0;
int window_bits;
if (NULL == strm)
{
goto ERROR;
}
strm->zalloc = Z_NULL;
strm->zfree = Z_NULL;
strm->opaque = Z_NULL;
strm->next_in = Z_NULL;
strm->avail_in = 0;
window_bits = (int)type;
ret = deflateInit2(strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
{
goto ERROR;
}
return 0;
ERROR:
return -1;
}
int deflating(PZSTREAM strm, uint8_t *inbuf, uint32_t in_len, uint8_t *outbuf, uint32_t *out_len)
{
int ret = 0;
if (NULL == strm || NULL == inbuf || NULL == outbuf || NULL == out_len)
{
goto ERROR;
}
ret = deflateReset(strm);
if (ret != Z_OK)
{
goto ERROR;
}
strm->next_in = inbuf;
strm->avail_in = in_len;
strm->next_out = outbuf;
strm->avail_out = *out_len;
// 此函数的应用场景都是小buffer场景,所以这里inbuf的传入都是完整需要压缩的,
// 所以这里直接调用Z_FINISH
ret = deflate(strm, Z_FINISH);
if (ret != Z_STREAM_END)
{
goto ERROR;
}
*out_len = strm->total_out;
return 0;
ERROR:
return -1;
}
int deflate_end(PZSTREAM strm)
{
int ret = 0;
ret = deflateEnd(strm);
if (ret != Z_OK)
{
goto ERROR;
}
return 0;
ERROR:
return -1;
}
int inflate_beg(PZSTREAM strm, zip_type type)
{
int ret = 0;
int window_bits = 0;
if (NULL == strm)
{
goto ERROR;
}
strm->zalloc = (alloc_func)0;
strm->zfree = (free_func)0;
strm->opaque = (voidpf)0;
window_bits = (int)type;
ret = inflateInit2(strm, window_bits);
if (ret != Z_OK)
{
goto ERROR;
}
return 0;
ERROR:
return -1;
}
int inflating(PZSTREAM strm, uint8_t *inbuf, uint32_t in_len, uint8_t *outbuf, uint32_t *outlen)
{
int ret = 0;
if (NULL == strm || NULL == inbuf || NULL == outbuf || NULL == outlen)
{
goto ERROR;
}
ret = inflateReset(strm);
if (ret != Z_OK)
{
goto ERROR;
}
strm->next_in = inbuf;
strm->avail_in = in_len;
strm->next_out = outbuf;
strm->avail_out = *outlen;
ret = inflate(strm, Z_FINISH);
if (Z_STREAM_END != ret)
{
goto ERROR;
}
*outlen = strm->total_out;
return 0;
ERROR:
return -1;
}
int inflate_end(PZSTREAM strm)
{
int ret = 0;
ret = inflateEnd(strm);
if (ret != Z_OK)
{
goto ERROR;
}
return 0;
ERROR:
return -1;
}