在字典編碼中,最常用的恐怕要算LZ77編碼了。LZ77的思想很簡單,就是用一個<offset, length>元組來表示當前位置的字節串在前offset個字節中出現過。正是由於這個簡單的思想,所有基於LZ77實現的實用算法都有着不錯的解壓速度。經典的使用LZ77編碼的壓縮算法有zip/gz的deflate算法,7z的lzma算法等。
在對LZ77算法研究中,我們也發現算法中的一些不足之處,LZ77最明顯的不足是offset值的過度零散導致對<offset, length>元組的后續處理效果不好。例如處理一個16MB的數據塊,一個<offset, length>中offset的取值就有16777216種之多,雖然可以對offset進行分段處理,但還是或多或少會影響到壓縮率的提升。
於是我們有了ROLZ算法,ROLZ全稱是Reduced Offset Lempel Ziv,即減少了offset的LZ編碼。在ROLZ中,重復串出現的位置不再是用相對當前的偏移量offset,而是用一個表項編號index來表示。
在詳細介紹ROLZ算法之前,我們先介紹一個壓縮算法中常用的概念——上下文(context),上下文就是當前編碼位置之前的內容,在實際應用中,我們只取當前編碼位置前k個字符做為上下文,稱為k階上下文。上下文是提高壓縮率的一個重要工具,例如字符串"queen",如果用一般的統計模型來處理,由於英語中字母"u"出現頻率不高,我們可能會給"u"分配一個長的前綴編碼。但考慮一階上下文"q",在英語中"q"后面出現"u"的概率非常高,我們就會給"u"分配相對較短的前綴編碼,從而利用上下文提高了編碼效率。
在編碼中,我們總會保存每個上下文的一些信息,但是當上下文階數k >= 3時,上下文的個數會急劇升高,當k=4時就已經有256^4 = 4GB的上下文各數。所以對於較長的上下文,我們往往取一個對前k個字符的一個Hash值作為上下文,這樣的結果是可能幾個完全不同的上下文會占用相同的存儲空間,導致上下文的預測准確率有所降低,但這種做法滿足了程序對內存的要求,實際中也被廣泛使用。
現在進入正題,在ROLZ算法中,我們會建立一張二維表rolz_table[context][index],在處理完每個位置后,我們將這個位置存入對應上下文的桶中,以下就字符串"construct- destruct"舉個例子(考慮一階上下文,假設當前編碼到位置12):
0 1 2 3 4 5 6 7 8 9 10 11 $ 13 14 15 16 17 c o n s t r u c t - d e s t r u c t table["c"] = {8, 1} table["o"] = {2} table["n"] = {3} table["s"] = {4} table["t"] = {9, 5} table["r"] = {6} table["u"] = {7} table["-"] = {10} table["d"] = {11}
如果是傳統的LZ77算法,這時候我們應該輸出一個匹配對<offset=9, length=6>表示重復串"struct"。但在ROLZ算法中,我們只在對應上下文的桶中尋找匹配,在當前上下文"e"的桶中找不到匹配(因為是一個空的桶),只好將"s"原樣輸出,然后將當前位置加入上下文"e"的桶中:
0 1 2 3 4 5 6 7 8 9 10 11 12 $ 14 15 16 17 c o n s t r u c t - d e s t r u c t table["c"] = {8, 1} table["o"] = {2} table["n"] = {3} table["s"] = {4} table["t"] = {9, 5} table["r"] = {6} table["u"] = {7} table["-"] = {10} table["d"] = {11} table["e"] = {12}
現在編碼位置13的"t",我們在上下文"s"的桶中尋找匹配,發現table["s"][0]就是我們要找的匹配,於是匹配成功,輸出一個ROLZ匹配項<index=0, length=5>表示當前位置的重復串"truct"。
所以,對字符串"construct-destruct",LZ77和ROLZ的編碼結果分別如下:
LZ77: construct-de<9,6> ROLZ: construct-des<0,5>
可以看到ROLZ的輸出多了一個字符"s",但是offset變成了更容易處理的index,實踐表明對大量輸入的時候ROLZ通常能得到更好的壓縮率。
ROLZ的編碼實現比LZ77要簡單得多,因為rolz_table本身就是一張很好的散列表,我們不需要像LZ77那樣另外創建一個查找結構。而解壓相對LZ77要復雜一些,我們必須和壓縮過程一樣構建起整個rolz_table,然后通過查表將index轉換為重復串的位置,所以ROLZ解壓相對LZ77要慢一些。
我個人有一個ROLZ+算術編碼的完整實現comprolz,目前壓縮率已經和7z相當。下面也附上一個簡單的ROLZ+Polar編碼實現(壓縮率略高於gz),使用3階Hash上下文,每個上下文有15個桶項:
1 /******************************************************************************* 2 * RichSelian's nooblike compressor: ROLZ + Polar coding 3 ******************************************************************************/ 4 #include <stdio.h> 5 #include <string.h> 6 7 /******************************************************************************* 8 * POLAR Coder 9 ******************************************************************************/ 10 #define POLAR_SYMBOLS 512 /* should be even */ 11 #define POLAR_MAXLEN 15 /* should be less than 16, so we can pack two length values into a byte */ 12 13 #define M_round_down(x) while((x)&(-(x)^(x))) { (x) &= (-(x)^(x)); } 14 #define M_round_up(x) while((x)&(-(x)^(x))) { (x) &= (-(x)^(x)); } (x) <<= 1; 15 #define M_int_swap(x, y) {int (_)=(x); (x)=(y); (y)=(_);} 16 17 int polar_make_leng_table(const int* freq_table, int* leng_table) { 18 int symbols[POLAR_SYMBOLS]; 19 int i; 20 int s; 21 int total; 22 int shift = 0; 23 24 memcpy(leng_table, freq_table, POLAR_SYMBOLS * sizeof(int)); 25 26 MakeTablePass: 27 /* sort symbols */ 28 for(i = 0; i < POLAR_SYMBOLS; i++) { 29 symbols[i] = i; 30 } 31 for(i = 0; i < POLAR_SYMBOLS; i++) { 32 if(i > 0 && leng_table[symbols[i - 1]] < leng_table[symbols[i]]) { 33 M_int_swap(symbols[i - 1], symbols[i]); 34 i -= 2; 35 } 36 } 37 38 /* calculate total frequency */ 39 total = 0; 40 for(i = 0; i < POLAR_SYMBOLS; i++) { 41 total += leng_table[i]; 42 } 43 44 /* run */ 45 M_round_up(total); 46 s = 0; 47 for(i = 0; i < POLAR_SYMBOLS; i++) { 48 M_round_down(leng_table[i]); 49 s += leng_table[i]; 50 } 51 while(s < total) { 52 for(i = 0; i < POLAR_SYMBOLS; i++) { 53 if(s + leng_table[symbols[i]] <= total) { 54 s += leng_table[symbols[i]]; 55 leng_table[symbols[i]] *= 2; 56 } 57 } 58 } 59 60 /* get code length */ 61 for(i = 0; i < POLAR_SYMBOLS; i++) { 62 s = 2; 63 if(leng_table[i] > 0) { 64 while((total / leng_table[i]) >> s != 0) { 65 s += 1; 66 } 67 leng_table[i] = s - 1; 68 } else { 69 leng_table[i] = 0; 70 } 71 72 /* code length too long -- scale and rebuild table */ 73 if(leng_table[i] > POLAR_MAXLEN) { 74 shift += 1; 75 for(i = 0; i < POLAR_SYMBOLS; i++) { 76 if((leng_table[i] = freq_table[i] >> shift) == 0 && freq_table[i] > 0) { 77 leng_table[i] = 1; 78 } 79 } 80 goto MakeTablePass; 81 } 82 } 83 return 0; 84 } 85 86 int polar_make_code_table(const int* leng_table, int* code_table) { 87 int i; 88 int s; 89 int t1; 90 int t2; 91 int code = 0; 92 93 memset(code_table, 0, POLAR_SYMBOLS * sizeof(int)); 94 95 /* make code for each symbol */ 96 for(s = 1; s <= POLAR_MAXLEN; s++) { 97 for(i = 0; i < POLAR_SYMBOLS; i++) { 98 if(leng_table[i] == s) { 99 code_table[i] = code; 100 code += 1; 101 } 102 } 103 code *= 2; 104 } 105 106 /* reverse each code */ 107 for(i = 0; i < POLAR_SYMBOLS; i++) { 108 t1 = 0; 109 t2 = leng_table[i] - 1; 110 while(t1 < t2) { 111 code_table[i] ^= (1 & (code_table[i] >> t1)) << t2; 112 code_table[i] ^= (1 & (code_table[i] >> t2)) << t1; 113 code_table[i] ^= (1 & (code_table[i] >> t1)) << t2; 114 t1++; 115 t2--; 116 } 117 } 118 return 0; 119 } 120 121 int polar_make_decode_table(const int* leng_table, const int* code_table, int* decode_table) { 122 int i; 123 int c; 124 125 for(c = 0; c < POLAR_SYMBOLS; c++) { 126 if(leng_table[c] > 0) { 127 for(i = 0; i + code_table[c] < 65536; i += (1 << leng_table[c])) { 128 decode_table[i + code_table[c]] = c; 129 } 130 } 131 } 132 return 0; 133 } 134 135 /******************************************************************************* 136 * ROLZ 137 ******************************************************************************/ 138 #define ROLZ_BUCKET_SIZE 65536 139 #define MATCH_IDX_SIZE 15 /* make element of rolz_table[] 64 Bytes */ 140 #define MATCH_LEN_MIN 2 141 #define MATCH_LEN_MAX 17 /* MATCH_LEN_MAX < MATCH_LEN_MIN + (POLAR_SYMBOLS-256) / MATCH_IDX_SIZE */ 142 143 static int ch1 = 0; 144 static int ch2 = 0; 145 static int ch3 = 0; 146 147 #define M_rolz_item(x, n) (rolz_table[(x)].m_item[(rolz_table[(x)].m_head + MATCH_IDX_SIZE - (n)) % MATCH_IDX_SIZE]) 148 149 static struct { 150 unsigned int m_item[MATCH_IDX_SIZE]; 151 unsigned char m_head; 152 } rolz_table[ROLZ_BUCKET_SIZE]; 153 154 static inline unsigned int rolz_context() { 155 return (unsigned)(ch1 * 131313131 + ch2 * 131313 + ch3 * 131) % ROLZ_BUCKET_SIZE; 156 } 157 158 static inline void rolz_update_context(unsigned char* buf, int pos, int cache) { 159 rolz_table[rolz_context()].m_head = (rolz_table[rolz_context()].m_head + 1) % MATCH_IDX_SIZE; 160 if(cache) { 161 M_rolz_item(rolz_context(), 0) = pos | (buf[pos] << 24); 162 } else { 163 M_rolz_item(rolz_context(), 0) = pos; 164 } 165 ch3 = ch2; 166 ch2 = ch1; 167 ch1 = buf[pos]; 168 return; 169 } 170 171 int rolz_encode(unsigned char* ibuf, unsigned short* obuf, int ilen) { 172 int olen = 0; 173 int pos = 0; 174 int i; 175 int j; 176 int match_idx; 177 int match_len; 178 179 memset(rolz_table, 0, sizeof(rolz_table)); 180 ch3 = 0; 181 ch2 = 0; 182 ch1 = 0; 183 while(pos < ilen) { 184 match_len = MATCH_LEN_MIN - 1; 185 match_idx = -1; 186 if(pos + MATCH_LEN_MAX < ilen) { /* find match */ 187 for(i = 0; i < MATCH_IDX_SIZE; i++) { 188 if(M_rolz_item(rolz_context(), i) == 0 || M_rolz_item(rolz_context(), i) >> 24 != ibuf[pos]) { 189 continue; 190 } 191 192 j = 1; 193 while(j < MATCH_LEN_MAX && ibuf[pos + j] == ibuf[(M_rolz_item(rolz_context(), i) & 0x00ffffff) + j]) { 194 j++; 195 } 196 if(j > match_len) { 197 match_len = j; 198 match_idx = i; 199 } 200 } 201 } 202 if(match_len < MATCH_LEN_MIN) { 203 match_len = 1; 204 match_idx = -1; 205 } 206 207 if(match_idx == -1) { /* encode */ 208 obuf[olen++] = ibuf[pos]; 209 } else { 210 obuf[olen++] = 256 + (match_len - MATCH_LEN_MIN) * MATCH_IDX_SIZE + match_idx; 211 } 212 213 for(i = 0; i < match_len; i++) { /* update context */ 214 rolz_update_context(ibuf, pos, 1); 215 pos += 1; 216 } 217 } 218 return olen; 219 } 220 221 int rolz_decode(unsigned short* ibuf, unsigned char* obuf, int ilen) { 222 int olen = 0; 223 int pos = 0; 224 int i; 225 int match_pos; 226 int match_idx; 227 int match_len; 228 229 ch3 = 0; 230 ch2 = 0; 231 ch1 = 0; 232 for(pos = 0; pos < ilen; pos++) { 233 if(ibuf[pos] < 256) { /* decode */ 234 match_idx = -1; 235 match_len = 1; 236 obuf[olen++] = ibuf[pos]; 237 } else { 238 match_idx = (ibuf[pos] - 256) % MATCH_IDX_SIZE; 239 match_len = (ibuf[pos] - 256) / MATCH_IDX_SIZE + MATCH_LEN_MIN; 240 } 241 242 if(match_idx != -1) { /* expand match */ 243 match_pos = M_rolz_item(rolz_context(), match_idx); 244 for(i =-0; i < match_len; i++) { 245 obuf[olen++] = obuf[match_pos + i]; 246 } 247 } 248 for(i = 0; i < match_len; i++) { /* update context */ 249 rolz_update_context(obuf, olen - match_len + i, 0); 250 } 251 } 252 return olen; 253 } 254 255 /******************************************************************************* 256 * MAIN 257 ******************************************************************************/ 258 int main(int argc, char** argv) { 259 static unsigned char ibuf[10000000]; /* blocksize = 10MB */ 260 static unsigned short rbuf[10000000]; 261 static unsigned char obuf[12000000]; 262 int ilen; 263 int rlen; 264 int olen; 265 int rpos; 266 int opos; 267 int i; 268 int freq_table[POLAR_SYMBOLS]; 269 int leng_table[POLAR_SYMBOLS]; 270 int code_table[POLAR_SYMBOLS]; 271 int decode_table[1 << (POLAR_MAXLEN + 1)]; 272 int code_buf; 273 int code_len; 274 275 if(argc == 2 && strcmp(argv[1], "e") == 0) { 276 while((ilen = fread(ibuf, 1, sizeof(ibuf), stdin)) > 0) { 277 rlen = rolz_encode(ibuf, rbuf, ilen); 278 olen = 0; 279 280 memset(freq_table, 0, sizeof(freq_table)); 281 code_buf = 0; 282 code_len = 0; 283 284 for(i = 0; i < rlen; i++) { 285 freq_table[rbuf[i]] += 1; 286 } 287 polar_make_leng_table(freq_table, leng_table); 288 polar_make_code_table(leng_table, code_table); 289 290 /* write length table */ 291 for(i = 0; i < POLAR_SYMBOLS; i += 2) { 292 obuf[olen++] = leng_table[i] * 16 + leng_table[i + 1]; 293 } 294 295 /* encode */ 296 for(i = 0; i < rlen; i++) { 297 code_buf += code_table[rbuf[i]] << code_len; 298 code_len += leng_table[rbuf[i]]; 299 while(code_len > 8) { 300 obuf[olen++] = code_buf % 256; 301 code_buf /= 256; 302 code_len -= 8; 303 } 304 } 305 if(code_len > 0) { 306 obuf[olen++] = code_buf; 307 code_buf = 0; 308 code_len = 0; 309 } 310 fwrite(&rlen, sizeof(rlen), 1, stdout); 311 fwrite(&olen, sizeof(olen), 1, stdout); 312 fwrite(obuf, 1, olen, stdout); 313 } 314 return 0; 315 } 316 if(argc == 2 && strcmp(argv[1], "d") == 0) { 317 while(fread(&rlen, sizeof(rlen), 1, stdin) == 1 && fread(&olen, sizeof(olen), 1, stdin) == 1) { 318 olen = fread(obuf, 1, olen, stdin); 319 rpos = 0; 320 opos = 0; 321 code_buf = 0; 322 code_len = 0; 323 324 /* read length table */ 325 for(i = 0; i < POLAR_SYMBOLS; i += 2) { 326 leng_table[i] = obuf[opos] / 16; 327 leng_table[i + 1] = obuf[opos] % 16; 328 opos++; 329 } 330 331 /* decode */ 332 polar_make_code_table(leng_table, code_table); 333 polar_make_decode_table(leng_table, code_table, decode_table); 334 335 while(rpos < rlen) { 336 while(opos < olen && code_len < POLAR_MAXLEN) { 337 code_buf += obuf[opos++] << code_len; 338 code_len += 8; 339 } 340 i = decode_table[code_buf % 65536]; 341 342 rbuf[rpos++] = i; 343 code_buf >>= leng_table[i]; 344 code_len -= leng_table[i]; 345 } 346 347 ilen = rolz_decode(rbuf, ibuf, rlen); 348 fwrite(ibuf, 1, ilen, stdout); 349 } 350 } 351 return -1; 352 }