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]中對應的值,而這個數本身代表該值的碼字長度。例如數列
“0、0、0、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 11000101 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、4、6、6、5、3、6、0、5、5、0、6、4、5、4、5、0、0、5、4、4、6、6、6、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、6、5、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
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)層討論的主要是網絡與網絡互連的問題。 在數據鏈路層(局域網)使用的信道主要有兩種:點對點信道和廣播信道,我們具體要討論