在《數據結構、算法與應用:C++語言描述》((美)Sartaj Sahni著;汪詩林等譯 北京:機械工業出版社,2000.1)第238頁提到了用於文本壓縮的LZW算法,該算法可以正確地處理文本文件,但對於二進制文件卻經常會出現將文件壓縮並解壓後得到的文件與原文件不同的問題。
該書中將該算法用於文本壓縮,而該算法又能正確處理文本文件,因而該算法在這個意義上說是正確的。
但壓縮算法應該能處理各種格式的文件。下面,我就對書上的算法稍做修改使之能進行二進制文件的壓縮。
1、問題分析
我對該算法進行黑盒測試,發現以下情況:
(1)該算法能正確處理文本文件,且不論文件長短,不論英文還是漢字都能處理。
(2)該算法對我測試的十幾個二進制文件進行壓縮並解壓後文件都變短了。
(3)懷疑是控制字符產生的影響,我在一個文本文件中了一些0x00和0xff等特殊字符,壓縮並解壓後出現問題。進行多次這樣的試驗,並將壓縮並解壓後的文件與原始文件進行比較,我發現特殊字符之前的內容均正確,而0x00之後的內容“面目全非”,其它特殊字符不會產生影響。而且,文件中所有0x00在壓縮並解壓後的文件中消失了,而其它字符均沒有問題(原文件中0x00後的字符在解壓後的文件中錯位了)。推測是0x00的問題,我在文本文件中添入特殊字符但不添加0x00,此時結果正確。
據上面推斷可知問題出現在該算法不能正確處理字符0x00。
將某一文本文件第一個字符改爲0x00,並用該文件作爲測試數據對壓縮程序進行調試。單步調試,當程序進入Compress函數,並執行到in.get(c)(原程序compress.cpp第83行)時,由於第一個字符是0x00,因此c的值爲0x00,到第84行,pcode的值變爲0x00,到第87行,程序又讀進一字符,c的值變爲一個非0x00的值,不妨將該值設爲C2,到第89行unsigned long k = (pcode << ByteSize) + c,k的值變爲C2(因pcode等於0x00)。當運行到第91行“if (h.Search(k, e)) pcode = e.code”時,程序會忽略那個被移到k高位的0x00字符。因前256個字符在前面已經加入字典,h.Search(k, e)返回真(因爲k=C2<256),pcode被賦值爲C2,這實際上是把讀進的前兩個字符當一個字符(第二個字符)處理。這樣就使0x00字符消失了。
1、問題解決
知道了問題的原因解決問題就不難了。一種方法是像數據鏈路層PPP協議把0x7e變換爲0x7d,0x5e並把0x7d變換爲0x7d,0x5d那樣將0x00變換成兩個其它字符,但這樣實現起來比較麻煩。
下面介紹一種改進方法,只用添、改四五行代碼即可實現。將key的20位(最低位爲第0位,低20位爲有效信息)作爲“多字符”標誌,即若該標誌位爲1則表示該key所譯出的字符數大於1,若該標誌位爲0則表示該key只能譯出一個字符。顯然,只有字典中低256個元素的key中“多字符”標誌爲0,以後添加的元素的key中“多字符”標誌都爲1。這樣,如果一個key的高位是0x00字符在字典中的code,因其“多字符”標誌爲1,程序也會將它按多字符處理。修改的方法即在查字典和向字典中添加元素時都使用key的“多字符”標誌。解壓程序不用修改,修改後程序已調試通過,可處理各種二進制文件。
注:
原程序和修改後的程序下載:http://files.myopera.com/langchibi_zhou/files/lzw.rar
我修改的行後面有“//Modified by Zhou Xiaowei on Apr 13,2007”,添加的行後面有“//Added by Zhou Xiaowei on Apr 13,2007”。
2007年4月17日