寫在前邊
最近在做一些關於網絡優化的一些事情,涉及到對純數據(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