zlib-Deflate壓縮算法

寫在前邊

最近在做一些關於網絡優化的一些事情,涉及到對純數據(Data、字符串、Json等)進行壓縮,用到了Deflate壓縮算法,這裏就簡單說一下如何用OC實現 Deflate

先看代碼 (後面會給出詳細解析)

Deflate.h

//
//  Deflate.h
//  CompressionTest

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Deflate : NSObject

+ (NSData *)compress: (NSData *)data;
+ (NSData *)decompress: (NSData *)data;
@end

NS_ASSUME_NONNULL_END

Deflate.m

//
//  Deflate.m
//  CompressionTest

#import "Deflate.h"

#include <zlib.h>

#define CHUNK 16384

@implementation Deflate

/**
 Deflate 壓縮
 @param data 需要壓縮的數據
 @return 返回壓縮數據
 */
+ (NSData *)compress:(NSData *)data {
    
    if ([data length] == 0) return data;
    // 初始化 z_stream
    z_stream stream;
    stream.zalloc = Z_NULL;
    stream.zfree = Z_NULL;
    stream.opaque = Z_NULL;
    stream.total_out = 0;
    stream.next_in = (Bytef *)[data bytes];
    stream.avail_in = (int)[data length];
    
    // Compresssion Levels:
    //   Z_NO_COMPRESSION
    //   Z_BEST_SPEED
    //   Z_BEST_COMPRESSION
    //   Z_DEFAULT_COMPRESSION
    
    if (deflateInit2(&stream,
                     Z_DEFAULT_COMPRESSION,
                     Z_DEFLATED,
                     -15,
                     MAX_MEM_LEVEL,
                     Z_DEFAULT_STRATEGY) != Z_OK) return nil;
    NSMutableData * compressed = [NSMutableData dataWithLength: CHUNK];
    do {
        if (stream.total_out >= [compressed length])
            // 追加長度
            [compressed increaseLengthBy:CHUNK];
        stream.next_out = [compressed mutableBytes] + stream.total_out;
        stream.avail_out = (uint)[compressed length] - (uint)stream.total_out;
        deflate(&stream, Z_FINISH);
        
    } while (stream.avail_out == 0);
    deflateEnd(&stream);
    [compressed setLength: stream.total_out];
    return [NSData dataWithData:compressed];
}


/**
 Deflate 解壓縮

 @param data 需要解壓數據
 @return 已經解壓的數據
 */
+ (NSData *)decompress:(NSData *)data {
    
    if ([data length] == 0) return data;
    // 初始化 z_stream
    z_stream strm;
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.total_out = 0;
    strm.next_in = (Bytef *)[data bytes];
    strm.avail_in = (int)[data length];

    unsigned full_length = (int)[data length];
    unsigned half_length = (int)[data length] / 2;
    NSMutableData * decompressed = [[NSMutableData alloc]
                                    initWithLength: full_length + half_length];
    BOOL done = false;
    int status;
    
    if (inflateInit2(&strm, -15) != Z_OK) return nil;
    while (!done) {
        if (strm.total_out >= [decompressed length])
            [decompressed increaseLengthBy: half_length];
        strm.next_out = [decompressed mutableBytes] + strm.total_out;
        strm.avail_out = (uint)[decompressed length] - (uint)strm.total_out;
        
        status = inflate(&strm, Z_SYNC_FLUSH);
        if (status == Z_STREAM_END)
            done = YES;
        else if (status != Z_OK)
            break;
    }
    if (inflateEnd(&strm) != Z_OK) return nil;
    if (done) {
        [decompressed setLength: strm.total_out];
        return [NSData dataWithData:decompressed];
    }
    return nil;
}

@end

相關概念

deflate(RFC1951):一種壓縮算法,使用LZ77和哈弗曼進行編碼;
zlib(RFC1950):一種格式,是對deflate進行了簡單的封裝,他也是一個實現庫(delphi中有zlib,zlibex)
gzip(RFC1952):一種格式,也是對deflate進行的封裝。

gzip = gzip頭 + deflate編碼的實際內容 + gzip
zlib = zlib頭 + deflate編碼的實際內容 + 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, intstrategy)

z_stream:這個是壓縮上下文,我們依照官方給的例子進行初始化

strm.zalloc = NULL; 
strm.zfree = NULL; 
strm.opaque = NULL;

strm.next_in = 你的待壓縮數據
strm.next_out = 壓縮以後數據存儲的buffer
strm.avail_in = 待壓縮數據的長度
strm.avail_out = 壓縮數據存儲buffer的長度.

level: 壓縮的等級,目前有四個值

#define Z_NO_COMPRESSION         0 	//不壓縮
#define Z_BEST_SPEED             1	//速度優先,可以理解爲最低限度的壓縮.
#define Z_BEST_COMPRESSION       9	//壓縮優先,但是速度會有些慢
#define Z_DEFAULT_COMPRESSION  (-1) //默認選項,compress裏面用的就是這個選項
/* compression levels */

method: 值只有一個,當前唯一的defalte壓縮方法,用於以後擴展

#define Z_DEFLATED   8
/* The deflate compression method (the only one supported in this version) */

windowBits: 窗口比特數

* 	-(15 ~ 8) : 純deflate壓縮
* 	+(15 ~ 8) : 帶zlib頭和尾
* 	> 16 : 帶gzip頭和尾

memLevel: 目前只有一個選項,MAX_MEM_LEVEL,無非是運行過程中對內存使用的限制.

/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
#  ifdef MAXSEG_64K
#    define MAX_MEM_LEVEL 8
#  else
#    define MAX_MEM_LEVEL 9
#  endif
#endif

strategy :用於調整壓縮算法,直接給默認就行Z_DEFAULT_STRATEGY.

#define Z_FILTERED            1	//用於由filter(或者稱爲predictor)生成的數據
#define Z_HUFFMAN_ONLY        2	//用於強制哈夫曼編碼(不做字符匹配)
#define Z_RLE                 3	//限制匹配長度爲1
#define Z_FIXED               4	//阻止使用動態哈夫曼編碼,從而允許獲得更簡單的解碼
#define Z_DEFAULT_STRATEGY    0	//用於普通數據
/* compression strategy; see deflateInit2() below for details */

Z_FILTERED,用於由filter(或者稱爲predictor)生成的數據.過濾的數據包含很多小的隨機數據。這種情況下,壓縮算法能夠獲得更好的壓縮效果。該選項可以強制更多的哈夫曼編碼和更少的字符匹配。有時候可以作爲Z_DEFAULT_STRATEGY和Z_HUFFMAN_ONLY的折衷。
Z_FIXED,阻止使用動態哈夫曼編碼,從而允許獲得更簡單的解碼。
strategy參數隻影響壓縮比,而不會影響到壓縮輸出的正確性,因此沒有正確的設置也不要緊。

deflate 壓縮函數

函數原型 :

ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));

解壓函數介紹

inflateInit2 初始化

原型函數

inflateInit2(z_streampstrm, int windowBits)

strm: 和deflate一樣,初始化三個回調以後即可,有的參考文檔說還需要初始化第四個選項,具體記不清哪個了,不過我試過以後發現貌似不用。

windownBits : 含義和deflateInit2一樣,而且一定要對應起來.

inflate 解壓

函數原型

ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));

z_streamp : 四個參數.

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.同樣,這個函數只適合單獨解壓場景,不適合需要多次傳入的場景.

相關鏈接:

https://yq.aliyun.com/articles/40985
https://github.com/tsolomko/SWCompression

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章