1. 程式人生 > >GZIP壓縮原理分析(32)——第五章 Deflate演算法詳解(五23) 動態哈夫曼編碼分析(12)構建哈夫曼樹(04)

GZIP壓縮原理分析(32)——第五章 Deflate演算法詳解(五23) 動態哈夫曼編碼分析(12)構建哈夫曼樹(04)

*構建literal/length樹

部落格http://www.cnblogs.com/esingchan/p/3958962.html中這樣說道:“ZIP之所以是通用壓縮,它實際上是針對位元組作為基本字元來編碼的,所以一個literal至多有256種可能”。Literal其實就是一個位元組所能表示的所有字元,包括可見與不可見的,從十進位制0到255,共256種。Length表示匹配串長度,匹配串最小長度為3,這點我們已經在LZ77章節中多次提到;匹配串長度不是無限的,最大長度為258,所以length共有256種,範圍對應閉區間[3, 258]。如果實際匹配長度超過258,後面的匹配部分就再用一個長度距離對兒表示。

為什麼literal和length的取值個數都是256,都可以用一個位元組表示,這是巧合嗎?雖然那個隱含假設告訴我們相同的內容總是扎堆兒出現,但為什麼length的最大值偏偏是258?一路分析過來,我們已經發現,壓縮中任何一個數據的設計都不是巧合,都是刻意為之,都是有實際意義的!這種設計可以讓literal和length共用同一棵哈夫曼樹,一次編碼同時得到literal和length的碼字。

Literal的範圍是閉區間[0, 255],length的範圍是閉區間[3, 258]。壓縮將這兩個區間編到同一張表中,從而將其整合到一起。閉區間[0, 255]仍然表示literal,關係是一對一的,一個數對應一個literal;256代表本壓縮塊的結束(前面我們講過壓縮是分塊進行的);length共有256種值,和distance一樣,為了優化,將這256個值分到不同的區間,共有29個區間,區間碼範圍為閉區間[257, 285]。如下表所示(length區間碼錶),

該表原理以及使用方法與distance區間碼錶是完全相同的。

閉區間[0, 285]將literal和length全部整合在一起,哈夫曼編碼就是針對這個閉區間進行的。[0, 256]是一對一的關係,參與哈夫曼編碼過程;[257, 285]是length的區間碼,這29個值參與哈夫曼編碼過程而不是256個length值參與哈夫曼編碼過程(與distance的使用方法是相同的)。壓縮結果也需要記錄閉區間[0, 285]這286個數的碼字長度,記錄方法與distance相同,使用一個數列,每個數在數列中的順序(從0開始)代表該數在閉區間[0, 285]中對應的值,而這個數本身代表該值的碼字長度。例如數列

000、0、0、0、0、0、1、0、0、2、0、0、0、0、4、0、0……”,

該數列共有286個數,第個0,表示閉區間[0, 258]中的零的碼字長度是0;第個0,表示閉區間[0, 258]中的一的碼字長度是0;第個0,表示閉區間[0, 258]中的二的碼字長度是0……第個1,表示閉區間[0, 258]中的八的碼字長度是1……第十一個2,表示閉區間[0, 258]中的十一的碼字長度是2……第十六個4,表示閉區間[0, 258]中的十六的碼字長度是4……

現在我們為字串“As mentioned above,there a(3,01)many kinds of wirelesssystem(3,0110)(4,100111) than cellular.”中的literal和length編碼。統計該字串中每個literal的出現頻率,並將[0, 258]中未在該字元出現的literal的頻率記為0;將閉區間[0, 258]中的256的出現頻率記為1,作為壓縮塊結束標記;該字串中的length為3、3、4,對應區間碼分別為257、257、258,其中,區間碼257出現兩次,區間碼258出現一次,其餘length區間碼出現頻率全部為0。

根據以上資訊,構造哈夫曼樹。因為原始哈夫曼樹各節點深度與正規化哈夫曼樹相同,所以簡單起見,我們這裡專門構造一棵正規化哈夫曼樹來說明。如下圖所示,

根據這棵樹我們可以得到literal和length的碼字,每個葉子節點都用閉區間[0, 285]中的值表示。Length區間碼為257和258的碼字分別為二進位制“11001”和“111111”;塊結束標記256的碼字為二進位制“111110”;其他各葉子節點都是literal的碼字。Length值的碼字也需要用“區間碼碼字+擴充套件位編碼”合成,合成方法與distance值的碼字合成方法相同,這裡不再贅述。

碼錶如下,

Literal碼錶

Literal值

ASCII字元

碼字直接計算結果

實際碼字(作為壓縮結果,從右往左看)

32

空格

000

000

101

e

001

100

97

a

0100

0010

108

l

0101

1010

110

n

0110

0110

115

s

0111

1110

116

t

1000

0001

100

d

10010

01001

104

h

10011

11001

105

i

10100

00101

109

m

10101

10101

111

o

10110

01101

114

r

10111

11101

121

y

11000

00011

44

,

110100

001011

46

.

110101

101011

65

A

110110

011011

98

b

110111

111011

99

c

111000

000111

102

f

111001

100111

107

k

111010

010111

117

u

111011

110111

118

v

111100

001111

119

w

111101

101111

256

111110

011111

從這張碼錶可以看出很多東西,比如字首碼、正規化哈夫曼編碼的各個性質、每層的碼字長度、出現頻率越高的字元碼字長度越短等。

Length碼錶

Length值

區間碼碼字

擴充套件位編碼

Length碼字(作為壓縮結果,從右往左看)

3

11001

無擴充套件位

10011

4

111111

無擴充套件位

111111

碼錶已經得到,現在就可以對著這兩張碼錶將字串“As mentioned above,there a(3,01)many kinds of wirelesssystem(3,0110)(4,100111) than cellular.”徹底從位元組流程式設計位元流。開始轉換:

“A(011011)s(1110) (000)m(10101)e(100)n(0110)t(0001)i(00101)o(01101)n(0110)e(100)d(01001) (000)a(0010)b(111011)o(01101)v(001111)e(100),(001011)t(0001)h(11001)e(100)r(11101)e(100)(000)a(0010) (3(10011),01)m(10101)a(0010)n(0110)y(00011)(000)k(010111)i(00101)n(0110)d(01001)s(1110)(000)o(01101)f(100111)(000)w(101111)i(00101)r(11101)e(100)l(1010)e(100)s(1110)s(1110)(000)s(1110)y(00011)s(1110)t(0001)e(100)m(10101) (3(10011),0110)(4(111111),100111) (000)t(0001)h(11001)a(0010)n(0110)(000)c(000111)e(100)l(1010)l(1010)u(110111)l(1010)a(0010)r(11101).(101011)”。

每個字元都已經找到了自己的碼字,但此時並不能直接用碼字把原碼替換掉!雖然現在已經是位元流,但在記憶體或儲存介質中,壓縮結果必須按照位元組的方式儲存,也就是說,這條位元流必須放到一個個的位元組中去!現在,每個碼字本身已經符合記憶體中的實際儲存方式(即原始計算結果的逆序),但是碼字與碼字之間還沒有符合實際的儲存方式。將碼字填入位元組時,要從該位元組的低位開始填起,比如,先將A填入一個位元組,再將s填入該位元組,那麼A的碼字佔據該位元組的低位而s的碼字佔據該位元組的高位,以此類推,只要保證從每個位元組的低位開始填起就沒有問題。如果一個位元組剩餘的位元位不足以放下整個碼字,則該碼字從右往左能放幾位元就放幾位元,這個碼字剩下那幾個位元就放到另外一個位元組中。一定要看懂這個地方,會直接影響到後面的原始碼分析的。

將A的碼字的低兩位放到另外一個位元組,原因我們後續分析。末尾要帶上256的碼字,如果不夠一個位元組,就用0補齊剩下的位元。不同的碼字用不同的顏色進行區分,填充開始:

11

11100110   10101000    10110100    00101000    11001101   10011000    00100000    01111011

01111011   10111000    01000100    01100110    00100111   01100100    01010110    11000101

00000110   10101110    01100010    11001001    11010001   01001110    10111100    10100101

01010011   11101001    00001110    00011111    00011110   10101100    11010011    11111110

00010011   10010001    1100010   01110000    01010000   11110101    01010110    11101001

11101011   00000111

每看一個位元組的時候,要從右往左看,就可以看出碼字,一個碼字可能會跨越兩個位元組。對應的十六進位制結果是(不看A讓出去的那兩位):

E6    A8    B4    28    CD    98    20    7B

7B    B8    44    66    27     64    56    C5

06    AE    62    C9    D1    4E    BC    A5

53    E9    0E    1F     1E    AC    D3    FE

13    91    C5   70     50    F5     56    E9

EB    07

最後用256的碼字表示該壓縮塊的結束,但是還剩4bits才夠一個位元組,所以剩下的4bits就全部用0補齊(這一點很重要,原始碼分析要用)。

這就是實際的壓縮結果,這個過程是我們完全用人工計算出來的,現在看看壓縮工具的壓縮結果,如下圖所示,

 和我們人工壓縮結果是完全相同的。

對著上面的位元流,有沒有產生這樣一個問題:這個位元流由兩張碼錶構成(literal碼錶和length碼錶同屬literal/length碼錶,為了說明方便才分開。另一個碼錶是distance碼錶),怎麼知道一個碼字該用哪張碼錶去解碼?其實很簡單,開始解碼的時候就用literal碼錶解碼,當發現一個碼字無法解碼時,說明這個碼字肯定是length的碼字,用length碼錶解碼;length後面跟著的肯定是distance碼字,所以解完length,後面的那個碼字就用distance碼錶解碼;解完distance碼字,再接著用literal碼錶解後續的碼字,重複這個過程即可。

之所以能這樣解碼,與壓縮時每個碼字放到位元流中的順序是分不開的。前面講LZ77的時候說過,待壓縮資料的首個字元不參與匹配過程,也就是說,長度距離對兒肯定不會在壓縮塊的開始位置出現(用腳趾頭都能想明白),所以解碼整個壓縮塊的首個碼字就用literal碼錶;每個長度距離對兒,distance的碼字都在length碼字的後面,從上面的位元流就可以看出,壓縮時先把length的碼字扔到位元流裡,再把distance的碼字扔到位元流裡,distance碼字緊跟在length碼字後面。

再次提醒,literal碼錶和length碼錶同屬literal/length碼錶,literal/length碼錶是“一”張表,與distance碼錶並列。

現在LZ77之後的結果已經全部轉換成了位元流,但是壓縮遠沒有結束,別忘了,還要記錄生成literal/length樹的資訊。閉區間[0, 285]中各值的碼字長度數列如下,

0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、3、0、0、0、0、0、0、0、0、0、0、0、6、0、6、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、6、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、466536、0、55、0、64545、0、0、544666、0、5、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、656、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0

Distance各區間碼的碼字長度數列如下,

0、0、0、2、0、0、0、0、1、0、2、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0

原始字串被壓縮之後的位元流如下,

11

11100110    10101000   10110100    00101000    11001101   10011000    00100000    01111011

01111011    10111000   01000100    01100110    00100111   01100100    01010110    11000101

00000110    10101110   01100010    11001001    11010001   01001110    10111100    10100101

01010011    11101001   00001110    00011111    00011110   10101100    11010011    11111110

00010011    10010001   11000101    01110000    01010000   11110101    01010110    11101001

11101011    00000111

這個時候的壓縮結果,已經基本具有了雛形,該有的基本都有了:碼字長度資訊+實際壓縮資料。但是壓縮並沒有結束,這並不是最優的壓縮結果,還有繼續壓縮的空間。

相關推薦

GZIP壓縮原理分析32—— Deflate演算法23 動態編碼分析12構建04

*構建literal/length樹 部落格http://www.cnblogs.com/esingchan/p/3958962.html中這樣說道:“ZIP之所以是通用壓縮,它實際上是針對位元組作為

GZIP壓縮原理分析29—— Deflate演算法20 動態編碼分析09構建01

現在已經完成了對字串“As mentioned above,there are many kinds of wireless systems other than cellular.”進行壓縮的第一步

GZIP壓縮原理分析19—— Deflate演算法10 演算法分析04 格式說明03 靜態編碼

靜態哈夫曼編碼(Compression with fixed Huffman codes),這部分內容只要看格式就好,出現在這裡的碼錶只是為了說明,細節此時可能不懂,但是後面會鋪開來講,不用擔心。

GZIP壓縮原理分析31—— Deflate演算法22 動態編碼分析11構建03

*構建distance樹 現在已經知道壓縮會在壓縮結果中儲存葉子節點深度資訊(即碼字長度)從而讓解壓方間接得到碼錶,但是問題來了,構造樹的資訊只包括碼字長度,可解壓方怎麼知道這個碼字長度是哪個原碼的(注意,“原碼”與“原始碼”的差別,前者是指原始資料,後者是指程式碼)?有什

GZIP壓縮原理分析30—— Deflate演算法21 動態編碼分析10構建02

*正規化哈夫曼編碼 使用靜態哈夫曼編碼的編碼/解碼雙方同時擁有一張完全相同的碼錶,這張碼錶是事先規定好的,只要使用這種壓縮方式並且使用這種壓縮方式對應的靜態哈夫曼編碼,那麼壓縮方就照著碼錶壓縮,解碼方

乾貨 | 深度學習之卷積神經網路CNN的前向傳播演算法

微信公眾號 關鍵字全網搜尋最新排名 【機器學習演算法】:排名第一 【機器學習】:排名第一 【Python】:排名第三 【演算法】:排名第四 前言 在(乾貨 | 深度學習之卷積神經網路(CNN)的模型結構)中,我們對CNN的模型結構做了總結,這裡我們就在CNN的模型基礎上,看看CNN的前向傳播演算法是什麼樣

、DispatcherServlet

3.1、DispatcherServlet作用 DispatcherServlet是前端控制器設計模式的實現,提供Spring Web MVC的集中訪問點,而且負責職責的分派,而且與Spring IoC容器無縫整合,從而可以獲得Spring的所有好處。 具體請參考第二章的圖

GZIP壓縮原理分析04—— gzip檔案格式三02 gzip檔案頭

檔案頭由固定長度的部分和擴充套件部分組成,擴充套件部分不一定存在,尤其是網路傳輸使用的HTTP壓縮,如果使用了gzip格式,那麼對應的壓縮報文一般都不帶擴充套件部分。gzip檔案格式通過將頭部中定長部

GZIP壓縮原理分析07—— 基於gzip的HTTP壓縮四01 前語

簡單來講,HTTP壓縮就是將HTTP應答報文資料部分壓縮(所謂資料部分,是用於區分HTTP頭的),這對於減小網路頻寬來講有極大的好處。目前大型網站基本都會使用HTTP壓縮功能,比如百度、騰訊、新浪等,

XSS的原理分析與解剖:技巧篇**************未看*****************

第二章 != chrom 插入 是把 調用 bject innerhtml ats ??0×01 前言: 關於前兩節url: 第一章:http://www.freebuf.com/articles/web/40520.html 第二章:http://www.free

劉軍《社會網路分析導論》閱讀筆記3---

第六章 凝聚子群分析 社會結構研究的兩種視角:質的研究和量的研究 質的研究: 量的研究:網路研究 凝聚子群 派系 與成分割槽分:成分是任意兩點都可達的圖 缺點:要求太嚴格! n派系 注意:概念中說的是總圖!! 缺點: n宗派

劉軍《社會網路分析導論》閱讀筆記5---規則對等性

規則對等性引入 用著色方式引出規則對等性 規則對等性的測量 基本概念 一個圖中節點的型別總共可分成四類: 1.源點:無入度,有出度。 2.中介點:有入度,有出度。 3.終點:有入度,無出度。 4.孤立點:無入度,無出度。 按照上述四種點的分類,

Python語言程式設計MOOC崇天程式設計方法學學習筆記體育競技分析+第三方庫安裝腳步+os庫

複習: 數字型別及操作: 字串型別及操作: 程式的分支結構: 程式的迴圈結構: 函式的定義與使用: 程式碼複用與函式遞迴 集合型別及操作: 序列型別及操作: 字典型別及操作: 檔案的使用: 一維資料的格式化和處理:

Excel在統計分析中的應用——抽樣分佈-小樣本的抽樣分佈F分佈概率密度函式圖

F分佈的概率密度函式圖看上去還是比較平易近人的,不像卡方分佈那樣章亂無序。 Excel計算公式: C362==GAMMA((C$360+C$361)/2)*POWER(C$360,C$360/2)*POWER(C$361,C$361/2)*POWER($B362,C$360

Excel在統計分析中的應用——抽樣分佈-小樣本的抽樣分佈t分佈

貌似t分佈是比較有意思的一種概率分佈。 “ 在概率論和統計學中,學生t-分佈(t-distribution),可簡稱為t分佈,用於根據小樣本來估計呈正態分佈且方差未知的總體的均值。如果總體方差已知(例如在樣本數量足夠多時),則應該用正態分佈來估計總體均值。 t分佈曲線形態

Python語言程式設計MOOC崇天python計算生態概述學習筆記霍蘭德人格分析雷達圖+玫瑰花製作

複習:    今日學習: python計算生態概述 從資料出來到人工智慧 python庫之資料分析 numpy: pandas: Scipy: python庫之資料視覺化 Matpl

《深入理解Spark-核心思想與源碼分析存儲體系

配置信息 ger nbsp 效率 提升 理解 hadoop 任務 深入 天行健,君子以自強不息;地勢坤,君子以厚德載物。——《易經》 本章導讀   Spark的初始化階段、任務提交階段、執行階段,始終離不開存儲體系。   Spark為了避免Hadoop讀寫磁盤的I/O操

GZIP壓縮原理分析01——第一 序言

本系列部落格將詳細分析當前主流壓縮技術gzip的原理及其原始碼(gzip1.2.4)。暫時只分析無失真壓縮(如果後續我能有機會研究有失真壓縮,依然會以類似的形式將分析結果釋出到部落格中),並對當前網路

Javascript 高級程序設計3版 - 01

lock 文檔 瀏覽器對象模型 文檔對象模型 src 對象模型 world! block head 2017-05-10 js簡介 一個叫“不難登”的人發明的。js的流行是因為 ajax 的關系。 js分為三個部分: 核心: ECMA

計算機網絡謝希仁版——導讀1

時有 互連 如何實現 遠的 共享 esc 了解 網絡 是否   ※數據鏈路層討論什麽   數據鏈路層討論的是局域網中主機與主機間的連接問題,網絡(IP)層討論的主要是網絡與網絡互連的問題。   在數據鏈路層(局域網)使用的信道主要有兩種:點對點信道和廣播信道,我們具體要討論