RLE
RLE
又叫
Run Length Encoding
,是一個針對無損壓縮的非常簡單的算法。它用重復字節和重復的次數來簡單描述來代替重復的字節。盡管簡單并且對于通常的壓縮非常低效,但它有的時候卻非常有用(例如,
JPEG
就使用它)。
1.1.
原理
圖
2.1
顯示了一個如何使用
RLE
算法來對一個數據流編碼的例子,其中出現六次的符號‘
93
’已經用
3
個字節來代替:一個標記字節(‘
0
’在本例中)重復的次數(‘
6
’)和符號本身(‘
93
’)。
RLE
解碼器遇到符號‘
0
’
的時候,它表明后面的兩個字節決定了需要輸出哪個符號以及輸出多少次。
1.2.
實現
RLE
可以使用很多不同的方法。基本壓縮庫中詳細實現的方式是非常有效的一個。一個特殊的標記字節用來指示重復節的開始,而不是對于重復非重復節都
coding run
。
因此非重復節可以有任意長度而不被控制字節打斷,除非指定的標記字節出現在非重復節(頂多以兩個字節來編碼)的稀有情況下。為了最優化效率,標記字節應該是輸入流中最少出現的符號(或許就不存在)。
重復
runs
能夠在
32768
字節的時候運轉。少于
129
字節的要求
3
個字節編碼(標記
+
次數
+
符號),而大雨
128
字節要求四個字節(標記
+
次數的高
4
位
|0x80+
次數的低
4
位)。這是通常所有采用的壓縮的做法,并且也是相比較三個字節固定編碼(允許使用
3
個字節來編碼
256
個字節)而言非常少見的有損壓縮率的方法。
在這種模式下,最壞的壓縮結果是:
輸出大小
=257/256*
輸入大小
+1
2.??
哈夫曼
哈夫曼編碼是無損壓縮當中最好的方法。它使用預先二進制描述來替換每個符號,長度由特殊符號出現的頻率決定。常見的符號需要很少的位來表示,而不常見的符號需要很多為來表示。
哈夫曼算法在改變任何符號二進制編碼引起少量密集表現方面是最佳的。然而,它并不處理符號的順序和重復或序號的序列。
2.1.
原理
我不打算探究哈夫曼編碼的所有實際的細節,但基本的原理是為每個符號找到新的二進制表示,從而通常符號使用很少的位,不常見的符號使用較多的位。
簡短的說,這個問題的解決方案是為了查找每個符號的通用程度,我們建立一個未壓縮數據的柱狀圖;通過遞歸拆分這個柱狀圖為兩部分來創建一個二叉樹,每個遞歸的一半應該和另一半具有同樣的權(權是
∑
N
K
=1
符號數
k
, N
是分之中符號的數量,符號數
k
是符號
k
出現的次數
)
這棵樹有兩個目的:
1.
?
編碼器使用這棵樹來找到每個符號最優的表示方法
2.
?
解碼器使用這棵樹唯一的標識在壓縮流中每個編碼的開始和結束,其通過在讀壓縮數據位的時候自頂向底的遍歷樹,選擇基于數據流中的每個獨立位的分支,一旦一個到達葉子節點,解碼器知道一個完整的編碼已經讀出來了。
我們來看一個例子會讓我們更清楚。圖
2.2
顯示了一個
10
個字節的未壓縮的數據。
根據符號頻率,哈夫曼編碼器生成哈夫曼樹(圖
2.4
)和相應的編碼表示(圖
2.3
)。
?
你可以看到,常見的符號接近根,因此只要少數位來表示。于是最終的壓縮數據流如圖
2.5
所示。
壓縮后的數據流是
24
位(三個字節),原來是
80
位(
10
個字節)。當然,我應該存儲哈夫曼樹,這樣解碼器就能夠解碼出對應的壓縮流了,這就使得該例子中的真正數據流比輸入的流數據量大。這是相對較短的數據上的副作用。對于大數據量來說,上面的哈夫曼樹就不占太多比例了。
解碼的時候,從上到下遍歷樹,為壓縮的流選擇從左
/
右分支,每次碰到一個葉子節點的時候,就可以將對應的字節寫到解壓輸出流中,然后再從根開始遍歷。
2.2.
實現
哈夫曼編碼器可以在基本壓縮庫中找到,其是非常直接的實現。
這個實現的基本缺陷是:
1.
?
慢位流實現
2.
?
相當慢的解碼(比編碼慢)
3.
?
最大的樹深度是
32
(編碼器在任何超過
32
位大小的時候退出)。如果我不是搞錯的話,這是不可能的,除非輸出的數據大于
2
32
字節。
另一方面,這個實現有幾個優點:
1.
?
哈夫曼樹以一個緊密的形式每個符號要求
12
位(對于
8
位的符號)的方式存儲,這意味著最大的頭為
384
。
2.
?
編碼相當容易理解
哈夫曼編碼在數據有噪音的情況(不是有規律的,例如
RLE
)下非常好,這中情況下大多數基于字典方式的編碼器都有問題。
3.??
Rice
對于由大
word
(例如:
16
或
32
位)組成的數據和教低的數據值,
Rice
編碼能夠獲得較好的壓縮比。音頻和高動態變化的圖像都是這種類型的數據,它們被某種預言預處理過(例如
delta
相鄰的采樣)。
盡管哈夫曼編碼處理這種數據是最優的,卻由于幾個原因而不適合處理這種數據(例如:
32
位大小要求
16GB
的柱狀圖緩沖區來進行哈夫曼樹編碼)。因此一個比較動態的方式更適合由大
word
組成的數據。
3.1.
原理
Rice
編碼背后的基本思想是盡可能的用較少的位來存儲多個字(正像使用哈夫曼編碼一樣)。實際上,有人可能想到
Rice
是靜態的哈夫曼編碼(例如,編碼不是由實際數據內容的統計信息決定,而是由小的值比高的值常見的假定決定)。
編碼非常簡單:將值
X
用
X
個‘
1
’位之后跟一個
0
位來表示。
3.2.
實現
在基本壓縮庫針對
Rice
做了許多優化:
1.
?
每個字最沒有意義的位被存儲為
k
和最有意義的
N-k
位用
Rice
編碼。
K
作為先前流中少許采樣的位平均數。這是通常最好使用
Rice
編碼的方法,隱藏噪音且對于動態變化的范圍并不導致非常長的
Rice
編碼。
2.
?
如果
rice
編碼比固定的開端長,
T
,一個可選的編碼:輸出
T
個‘
1
’位,緊跟(
log2(X-T)
)個‘
1
’和一個‘
0
’位,接著是
X-T
(最沒有意義的
(log2(X-T))-1
位)。這對于大值來說都是比較高效的代碼并且阻止可笑的長
Rice
編碼(最壞的情況,對于一個
32
位
word
單個
Rice
編碼可能變成
2
32
位或
512MB
)。
如果開端是
4
,下面是結果編碼表:
X
|
bin
|
Rice
|
Thresholded
|
Rice
|
0
|
00000
|
0
|
0
|
?
|
1
|
00001
|
10
|
10
|
?
|
2
|
00010
|
110
|
110
|
?
|
3
|
00011
|
1110
|
1110
|
?
|
4
|
00100
|
11110
|
11110
|
?
|
5
|
00101
|
111110
|
111110
|
?
|
6
|
00110
|
1111110
|
11111100
|
+1
|
7
|
00111
|
11111110
|
11111101
|
?
|
8
|
01000
|
111111110
|
1111111000
|
+1
|
9
|
01001
|
1111111110
|
1111111001
|
?
|
10
|
01010
|
11111111110
|
1111111010
|
-1
|
11
|
01011
|
111111111110
|
1111111011
|
-2
|
12
|
01100
|
1111111111110
|
111111110000
|
?
|
13
|
01101
|
11111111111110
|
111111110001
|
-1
|
14
|
01110
|
111111111111110
|
111111110010
|
-2
|
15
|
01111
|
1111111111111110
|
111111110011
|
-3
|
16
|
10000
|
11111111111111110
|
111111110100
|
-4
|
17
|
10001
|
111111111111111110
|
111111110101
|
-5
|
18
|
10010
|
1111111111111111110
|
111111110110
|
-6
|
19
|
10011
|
11111111111111111110
|
111111110111
|
-7
|
20
|
10100
|
111111111111111111110
|
11111111100000
|
-5
|
就像你看到的一樣,在這個實現中使用
threshold
方法僅僅兩個編碼導致一個最壞的情況;剩下的編碼產生比標準
Rice
編碼還要短的編碼。
3.
?
最壞的情況,輸出。
4.??
Lempel-Ziv (LZ77)
Lempel-Ziv
壓縮模式有許多不同的變量?;緣嚎s庫有清晰的
LZ77
算法的實現(
Lempel-Ziv
,
1977
),執行的很好,源代碼也非常容易理解。
LZ
編碼器能用來通用目標的壓縮,特別對于文本執行的很好。它也在
RLE
和哈夫曼編碼器(
RLE
,
LZ
,哈夫曼)中使用來大多數情況下獲得更多的壓縮。
4.1.
原理
在
LZ
壓縮算法的背后是使用
RLE
算法用先前出現的相同字節序列的引用來替代。
簡單的講,
LZ
算法被認為是字符串匹配的算法。例如:在一段文本中某字符串經常出現,并且可以通過前面文本中出現的字符串指針來表示。當然這個想法的前提是指針應該比字符串本身要短。
例如,在上一段短語“字符串”經常出現,可以將除第一個字符串之外的所有用第一個字符串引用來表示從而節省一些空間。
一個字符串引用通過下面的方式來表示:
1.
?
唯一的標記
2.
?
偏移數量
3.
?
字符串長度
由編碼的模式決定引用是一個固定的或變動的長度。后面的情況經常是首選,因為它允許編碼器用引用的大小來交換字符串的大小(例如,如果字符串相當長,增加引用的長度可能是值得的)。
4.2.
實現
使用
LZ77
的一個問題是由于算法需要字符串匹配,對于每個輸入流的單個字節,每個流中此字節前面的哪個字節都必須被作為字符串的開始從而盡可能的進行字符串匹配,這意味著算法非常慢。
另一個問題是為了最優化壓縮而調整字符串引用的表示形式并不容易。例如,必須決定是否所有的引用和非壓縮字節應該在壓縮流中的字節邊界發生。
基本壓縮庫使用一個清晰的實現來保證所有的符號和引用是字節對齊的,因此犧牲了壓縮比率,并且字符串匹配程序并不是最優化的(沒有緩存、歷史緩沖區或提高速度的小技巧),這意味著程序非常慢。
另一方面,解壓縮程序非常簡單。
一個提高
LZ77
速度的試驗已經進行了,這個試驗中使用數組索引來加速字符串匹配的過程。然而,它還是比通常的壓縮程序慢。