標籤:
官方手冊 :
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_CODEint 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 __cplusplusextern "C" {#endiftypedef 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;}
zlib學習小結