zlib学习小结

官方手册 : 

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 :

函数原型 : ZEXTERN 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;
}

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值