程式僅為自己學習之用。關於範式huffman的介紹http://blog.pfan.cn/lingdlz/36436.html前面寫了huffman壓縮,解壓縮的程式
http://www.cnblogs.com/rocketfan/archive/2009/09/12/1565610.html
程式改寫了一下,加入了範式huffman壓縮,解壓縮。
實現在設計上利用compressor.py,decompressor.py定義兩個架構類給出壓縮,解壓縮的架構流程,huffman和範式huffman繼承這兩個架構,並給出不同的實現,同時範式huffman的壓縮會複用一部分huffman壓縮的函數實現。利用list,和索引,實現合并分組,來類比二叉樹的建立,並未實際建立二叉樹,來計算每個字元對應的encoding應該有的長度,也即葉子深度(等同與huffman編碼的長度)。具體就是每次在優先對列中選擇兩個權重最小的節點,然後將兩個節點所對應的葉子組裡面的所有葉子(字元)的深度加1,再將合并後的節點放入隊列(權重相加,同時合并兩個葉子分組為一組)。注意由於建立二叉樹的時候選擇最小的兩個節點時可能會有多個相同的最小節點供選擇,所以huffman編碼並不唯一。得到每個字元的encoding的長度後,具體encode的時候從encoding length從小到大排序,進行編碼。也即頻率大的字元先編碼,加入最小的encoding length為3則第一個編碼為000,和http://blog.pfan.cn/lingdlz/36436.html中介紹的實現唯一的不同是沒有記錄所有encoding length,對應的第一個encoding而是記錄最後一個,這樣實現更方便些。如length
3,
3
,
5
,
5
,
6
,6 ,6
,6 分別記錄紅色位置對應的encoding。3 0003 0015 010005 010016 0101006 0101016 0101106 010111這樣編碼的好處是,從000到010111按照字典序,字串比較是從小到大的。同時考慮 000 = 0 001 = 1 01000 = 8 01001 = 9 010100 = 20.... 010111 = 23 即編碼對應的int值也是從小到大的。這裡一個方案是記錄編碼資訊A(3,1),(5,9),(6,23),注意由於編碼可能有比較長的如19位,則不能一個byte儲存數值,具體實現的時候還是儲存成B (3, 001), (5, 01001),(6, 010111),解碼的時候在恢複出A的形式。下面是最基本的一個範式huffman解碼方法,每次讀1byte判斷解碼 1 num = 0
2 length = -1 #the actual encoding length - 1
3 while 1:
4 c = self.infile.read(1)
5 if c == '':
6 break
7 li = huffman.convert(c) #'a',asc 97,返回 一個list 內容是97對應的各個位元字
8 for x in li:
9 num = (num << 1) + int(x) #TODO num = (num << 1) & int(x) quick??
10 length += 1
11 if(num <= self.decoder.last_list[length]):
12 index = self.decoder.index_list[length] - (self.decoder.last_list[length] - num)
13 self.outfile.write(self.decoder.key_list[index]) #OK we decode one character
14 num = 0
15 length = -1
正確性壓縮率和huffman壓縮一樣(顯然的:)),無論採用那種壓縮,最後解壓縮後得到的文本和原文本如有不同,是因為原文本最後有多餘的分行符號,python讀檔案的時候會忽略最後多餘的分行符號號。
速度速度上,當前的實現兩種壓縮差不多。不過都慢的很,呵呵,壓縮一個24M的文本,得到13M的文本用時1分多,解壓縮要4分多。
TODO1. 對於範式huffman解壓縮來說,可以考慮在演算法層次上的,解壓縮最佳化,包括
查表等方法,可參考http://blog.csdn.net/Goncely/archive/2006/03/09/619725.aspx
TODO2. 具體剖析器速度瓶頸,那些地方影響了速度,是讀寫檔案慢還是其它的地方包括讀寫後的轉換。能否利用將核心地方C預言書實現或者參
考http://syue.com/Programming/Procedures/Python/73952.html提高程式速度。
TODO3. 有無現成的python轉碼器參考,總覺得輸入,輸出的處理有些繁瑣可能影響速度。
TODO4. 參考學習gzip的實現,
LZ..演算法的實現。
TODO5. 改用c++實現,看速度差異。
TODO6. 考慮分詞,
用詞w
ord
作為編碼解碼的單元,而不是現在的
字元character(byte),對比速度和壓縮率。用詞做編碼單元會使得要編碼的符號數目更多,更能體現 範式huffman的優點。
當前程式
/Files/rocketfan/glzip.rar
def usage(): """glzip 1.txt #will compress 1.txt to 1.txt.crs using huffman method glzip -d 1.txt.crs #will decompress 1.txt.crs to 1.txt.crs.de glzip -c 1.txt #will cocompress 1.txt to 1.txt.crs2 using canonical huffman method glzip -d -c 1.txt.crs2 #will decompress 1.txt.crs2 to 1.txt.crs2.de using canonical huffman method """程式的效能分析:首先分析採用普通的huffman方法壓縮檔,初步判斷,IO是程式瓶頸,按照一次讀檔案一個byte,一次寫檔案寫一個byte的方法,讀寫大檔案極其耗時。考慮下面的代碼,一次讀一個byte,讀完整個檔案,對於一個24M的檔案,利用cProfile在我的vmware,ubutu系統運行: time python -m cProfile ../glzip.py caesar-polo-esau.txt > 2.log1 def readFile(self):
2 self.infile.seek(0)
3 while 1:
4 c = self.infile.read(1)
5 if c == '':
6 break ompressing,huffman,encode character(byte)Compressing caesar-polo-esau.txt result is caesar-polo-esau.txt.crs 24292439 function calls in 59.029 CPU seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 31.151 31.151 59.018 59.018 compressor.py:16(readFile)耗時1分鐘。對於完整的huffman壓縮程式的其流程如下,調用huffman.Compressor.compress()1 def compress(self):
2 self.caculateFrequence()
3 self.genEncode()
4 self.__writeCompressedFile()
5
6 def __writeCompressedFile(self):
7 self.outfile.seek(0)
8 self.writeEncodeInfo()
9 self.encodeFile()可以看出,程式的時間幾乎全部花費在caculateFrequence()和encodeFile()上,分別對應讀檔案計算字元使用頻率,和編碼寫檔案。caculateFrequence() 會讀一遍輸入檔案,encodeFile()會再讀一遍輸入檔案,並編碼寫入輸出檔案。$./time python -m cProfile -s time ../glzip.py caesar-polo-esau.txt > 1.logcompressing,huffman,encode character(byte)Compressing caesar-polo-esau.txt result is caesar-polo-esau.txt.crs 74845611 function calls (74845219 primitive calls) in 263.437 CPU seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 1 117.427 117.427 192.423 192.423 huffman.py:124(encodeFile) 48584258 59.195 0.000 59.195 0.000 {method 'read' of 'file' objects} 1 43.486 43.486 70.979 70.979 huffman.py:77(caculateFrequence) 13127563 28.971 0.000 28.971 0.000 {method 'write' of 'file' objects}對比下僅僅執行讀寫相同檔案的函數的已耗用時間,僅僅執行下面的readFile和writeFile,顯然99%的時間花在了IO代價上。 1 def readFile(self):
2 self.infile.seek(0)
3 self.byteSum = 0
4 while 1:
5 c = self.infile.read(1)
6 self.byteSum += 1
7 if c == '':
8 break
9 def writeFile(self):
10 self.outfile.seek(0)
11 c = 'a'
12 for i in range(self.byteSum):
13 self.outfile.write(c)compressing,huffman,encode character(byte)Compressing caesar-polo-esau.txt result is caesar-polo-esau.txt.crs 48584571 function calls in 150.294 CPU seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 24292129 50.546 0.000 50.546 0.000 {method 'write' of 'file' objects} 1 37.005 37.005 65.026 65.026 compressor.py:16(readFile) 1 33.557 33.557 85.248 85.248 compressor.py:24(writeFile) 24292129 28.022 0.000 28.022 0.000 {method 'read' of 'file' objects}顯然第一個需要最佳化的地方是檔案的讀寫,一次讀寫1byte會帶來大量的read,write,影響效率。需要加入緩衝機制,先讀入緩衝區在處理,在http://blog.csdn.net/dwbclz/archive/2006/04/06/653294.aspx提到1.使用小塊的讀寫緩衝,經測試,緩衝大小在32K~64K之間效果比較好。我測試了一下對於讀,一次read(100)和一次read(1000)對於同樣的24M的檔案,耗時分別變為0.326,0.031,顯然是線性。