日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

文件压缩算法详细分析(ZIP)及解压实例解释

發(fā)布時(shí)間:2024/3/13 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 文件压缩算法详细分析(ZIP)及解压实例解释 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文地址:https://www.cnblogs.com/esingchan/p/3958962.html

最近自己實(shí)現(xiàn)了一個(gè)ZIP壓縮數(shù)據(jù)的解壓程序,覺(jué)得有必要把ZIP壓縮格式進(jìn)行一下詳細(xì)總結(jié),數(shù)據(jù)壓縮是一門(mén)通信原理和計(jì)算機(jī)科學(xué)都會(huì)涉及到的學(xué)科,在通信原理中,一般稱(chēng)為信源編碼,在計(jì)算機(jī)科學(xué)里,一般稱(chēng)為數(shù)據(jù)壓縮,兩者本質(zhì)上沒(méi)啥區(qū)別,在數(shù)學(xué)家看來(lái),都是映射。一方面在進(jìn)行通信的時(shí)候,有必要將待傳輸?shù)臄?shù)據(jù)進(jìn)行壓縮,以減少帶寬需求;另一方面,計(jì)算機(jī)存儲(chǔ)數(shù)據(jù)的時(shí)候,為了減少磁盤(pán)容量需求,也會(huì)將文件進(jìn)行壓縮,盡管現(xiàn)在的網(wǎng)絡(luò)帶寬越來(lái)越高,壓縮已經(jīng)不像90年代初那個(gè)時(shí)候那么迫切,但在很多場(chǎng)合下仍然需要,其中一個(gè)原因是壓縮后的數(shù)據(jù)容量減小后,磁盤(pán)訪(fǎng)問(wèn)IO的時(shí)間也縮短,盡管壓縮和解壓縮過(guò)程會(huì)消耗CPU資源,但是CPU計(jì)算資源增長(zhǎng)得很快,但是磁盤(pán)IO資源卻變化得很慢,比如目前主流的SATA硬盤(pán)仍然是7200轉(zhuǎn),如果把磁盤(pán)的IO壓力轉(zhuǎn)化到CPU上,總體上能夠提升系統(tǒng)運(yùn)行速度。壓縮作為一種非常典型的技術(shù),會(huì)應(yīng)用到很多很多場(chǎng)合下,比如文件系統(tǒng)、數(shù)據(jù)庫(kù)、消息傳輸、網(wǎng)頁(yè)傳輸?shù)鹊雀黝?lèi)場(chǎng)合。盡管壓縮里面會(huì)涉及到很多術(shù)語(yǔ)和技術(shù),但無(wú)需擔(dān)心,博主盡量將其描述得通俗易懂。另外,本文涉及的壓縮算法非常主流并且十分精巧,理解了ZIP的壓縮過(guò)程,對(duì)理解其它相關(guān)的壓縮算法應(yīng)該就比較容易了。

?

1、引子

壓縮可以分為無(wú)損壓縮和有損壓縮,有損,指的是壓縮之后就無(wú)法完整還原原始信息,但是壓縮率可以很高,主要應(yīng)用于視頻、話(huà)音等數(shù)據(jù)的壓縮,因?yàn)閾p失了一點(diǎn)信息,人是很難察覺(jué)的,或者說(shuō),也沒(méi)必要那么清晰照樣可以看可以聽(tīng);無(wú)損壓縮則用于文件等等必須完整還原信息的場(chǎng)合,ZIP自然就是一種無(wú)損壓縮,在通信原理中介紹數(shù)據(jù)壓縮的時(shí)候,往往是從信息論的角度出發(fā),引出香農(nóng)所定義的熵的概念,這方面的介紹實(shí)在太多,這里換一種思路,從最原始的思想出發(fā),為了達(dá)到壓縮的目的,需要怎么去設(shè)計(jì)算法。而ZIP為我們提供了相當(dāng)好的案例。

盡管我們不去探討信息論里面那些復(fù)雜的概念,不過(guò)我們首先還是要從兩位信息論大牛談起。因?yàn)槭撬麄兊旎私裉齑蠖鄶?shù)無(wú)損數(shù)據(jù)壓縮的核心,包括ZIP、RAR、GZIP、GIF、PNG等等大部分無(wú)損壓縮格式。這兩位大牛的名字分別是Jacob Ziv和Abraham Lempel,是兩位以色列人,在1977年的時(shí)候發(fā)表了一篇論文《A Universal Algorithm for Sequential Data Compression》,從名字可以看出,這是一種通用壓縮算法,所謂通用壓縮算法,指的是這種壓縮算法沒(méi)有對(duì)數(shù)據(jù)的類(lèi)型有什么限定。不過(guò)論文我覺(jué)得不用仔細(xì)看了,因?yàn)椴┲髯鳛橐幻ㄐ艑?zhuān)業(yè)的PHD,看起來(lái)也焦頭爛額,不過(guò)我們后面可以看到,它的思想還是很簡(jiǎn)單的,之所以看起來(lái)復(fù)雜,主要是因?yàn)镮EEE的某些雜志就是這個(gè)特點(diǎn),需要從數(shù)學(xué)上去證明,這種壓縮算法到底有多優(yōu),比如針對(duì)一個(gè)各態(tài)歷經(jīng)的隨機(jī)序列(不用追究什么叫各態(tài)歷經(jīng)隨機(jī)序列),經(jīng)過(guò)這樣的壓縮算法后,是否可以接近信息論里面的極限(也就是前面說(shuō)的熵的概念)等等,不過(guò)在理解其思想之前,個(gè)人認(rèn)為沒(méi)必要深究這些東西,除非你要發(fā)論文。這兩位大牛提出的這個(gè)算法稱(chēng)為L(zhǎng)Z77,兩位大牛過(guò)了一年又提了一個(gè)類(lèi)似的算法,稱(chēng)為L(zhǎng)Z78,思想類(lèi)似,ZIP這個(gè)算法就是基于LZ77的思想演變過(guò)來(lái)的,但ZIP對(duì)LZ77編碼之后的結(jié)果又繼續(xù)進(jìn)行壓縮,直到難以壓縮為止。除了LZ77、LZ78,還有很多變種的算法,基本都以L(fǎng)Z開(kāi)頭,如LZW、LZO、LZMA、LZSS、LZR、LZB、LZH、LZC、LZT、LZMW、LZJ、LZFG等等,非常多,LZW也比較流行,GIF那個(gè)動(dòng)畫(huà)格式記得用了LZW。我也寫(xiě)過(guò)解碼程序,以后有時(shí)間可以再寫(xiě)一篇,但感覺(jué)跟LZ77這些類(lèi)似,寫(xiě)的必要性不大。

ZIP的作者是一個(gè)叫Phil Katz的人,這個(gè)人算是開(kāi)源界的一個(gè)具有悲劇色彩的傳奇人物。雖然二三十年前,開(kāi)源這個(gè)詞還沒(méi)有現(xiàn)在這樣風(fēng)起云涌,但是總有一些具有黑客精神的牛人,內(nèi)心里面充滿(mǎn)了自由,無(wú)論他處于哪個(gè)時(shí)代。Phil Katz這個(gè)人是個(gè)牛逼程序員,成名于DOS時(shí)代,我個(gè)人也沒(méi)有經(jīng)歷過(guò)那個(gè)時(shí)代,我是從Windows98開(kāi)始接觸電腦的,只是從書(shū)籍中得知,那個(gè)時(shí)代網(wǎng)速很慢,撥號(hào)使用的是只有幾十Kb(比特不是字節(jié))的貓,56Kb實(shí)際上是這種貓的最高速度,在A(yíng)DSL出現(xiàn)之后,這種技術(shù)被迅速淘汰。當(dāng)時(shí)記錄文件的也是硬盤(pán),但是在電腦之間拷貝文件的是軟盤(pán),這個(gè)東西我大一還用過(guò),最高容量記得是1.44MB,這還是200X年的軟盤(pán),以前的軟盤(pán)容量具體多大就不知道了,Phil Katz上網(wǎng)的時(shí)候還不到1990年,WWW實(shí)際上就沒(méi)出現(xiàn),瀏覽器當(dāng)然是沒(méi)有的,當(dāng)時(shí)上網(wǎng)干嘛呢?基本就是類(lèi)似于網(wǎng)管敲各種命令,這樣實(shí)際上也可以聊天、上論壇不是嗎,傳個(gè)文件不壓縮的話(huà)肯定死慢死慢的,所以壓縮在那個(gè)時(shí)代很重要。當(dāng)時(shí)有個(gè)商業(yè)公司提供了一種稱(chēng)為ARC的壓縮軟件,可以讓你在那個(gè)時(shí)代聊天更快,當(dāng)然是要付費(fèi)的,Phil Katz就感覺(jué)到不爽,于是寫(xiě)了一個(gè)PKARC,免費(fèi)的,看名字知道是兼容ARC的,于是網(wǎng)友都用PKARC了,ARC那個(gè)公司自然就不爽,把哥們告上了法庭,說(shuō)牽涉了知識(shí)產(chǎn)權(quán)等等,結(jié)果Phil Katz坐牢了。。。牛人就是牛人, 在牢里面冥思苦想,決定整一個(gè)超越ARC的牛逼算法出來(lái),牢里面就是適合思考,用了兩周就整出來(lái)的,稱(chēng)為PKZIP,不僅免費(fèi),而且這次還開(kāi)源了,直接公布源代碼,因?yàn)樗惴ǘ疾灰粯恿?#xff0c;也就不涉及到知識(shí)產(chǎn)權(quán)了,于是ZIP流行開(kāi)來(lái),不過(guò)Phil Katz這個(gè)人沒(méi)有從里面賺到一分錢(qián),還是窮困潦倒,因?yàn)楹染七^(guò)多等眾多原因,2000年的時(shí)候死在一個(gè)汽車(chē)旅館里。英雄逝去,精神永存,現(xiàn)在我們用UE打開(kāi)ZIP文件,我們能看到開(kāi)頭的兩個(gè)字節(jié)就是PK兩個(gè)字符的ASCII碼。

?

2、一個(gè)案例的入門(mén)思考

好了,Phil Katz在牢里面到底思考了什么?用什么樣的算法來(lái)壓縮數(shù)據(jù)呢?我們想一個(gè)簡(jiǎn)單的例子:

生,容易。活,容易。生活,不容易。

上面這句話(huà)假如不壓縮,如果使用Unicode編碼,每個(gè)字會(huì)用2個(gè)字節(jié)表示。為什么是兩個(gè)字節(jié)呢?Unicode是一種國(guó)際標(biāo)準(zhǔn),把常見(jiàn)各國(guó)的字符,比如英文字符、日文字符、韓文字符、中文字符、拉丁字符等等全部制定了一個(gè)標(biāo)準(zhǔn),顯然,用2個(gè)字節(jié)可以最多表示2^16=65536個(gè)字符,那么65536就夠了嗎?生僻字其實(shí)是很多的,比如光康熙字典里面收錄的漢字就好幾萬(wàn),所以實(shí)際上是不夠的,那么是不是擴(kuò)到4個(gè)字節(jié)?也可以,這樣空間倒是變大了,可以收錄更多字符,但一方面擴(kuò)到4個(gè)字節(jié)就一定保證夠嗎?另一方面,4個(gè)字節(jié)是不是太浪費(fèi)空間了,就為了那些一般情況都不會(huì)出現(xiàn)的生僻字?所以,一般情況下,使用2個(gè)字節(jié)表示,當(dāng)出現(xiàn)生僻字的時(shí)候,再使用4個(gè)字節(jié)表示。這實(shí)際上就體現(xiàn)了信息論中數(shù)據(jù)壓縮基本思想,出現(xiàn)頻繁的那些字符,表示得短一些;出現(xiàn)稀少的,可以表示得長(zhǎng)些(反正一般情況下也不會(huì)出現(xiàn)),這樣整體長(zhǎng)度就會(huì)減小。除了Unicode,ASCII編碼是針對(duì)英文字符的編碼方案,用1個(gè)字節(jié)即可,除了這兩種編碼方案,還有很多地區(qū)性的編碼方案,比如GB2312可以對(duì)中文簡(jiǎn)體字進(jìn)行編碼,Big5可以對(duì)中文繁體字進(jìn)行編碼。兩個(gè)文件如果都使用一種編碼方案,那是沒(méi)有問(wèn)題的,不過(guò)考慮到國(guó)際化,還是盡量使用Unicode這種國(guó)際標(biāo)準(zhǔn)吧。不過(guò)這個(gè)跟ZIP沒(méi)啥關(guān)系,純屬背景介紹。

好了,回到我們前面說(shuō)的例子,一共有17個(gè)字符(包括標(biāo)點(diǎn)符號(hào)),如果用普通Unicode表示,一共是17*2=34字節(jié)。可不可以壓縮呢?所有人一眼都可以看出里面出現(xiàn)了很多重復(fù)的字符,比如里面出現(xiàn)了好多次容易(實(shí)際上是容易加句號(hào)三個(gè)字符)這個(gè)詞,第一次出現(xiàn)的時(shí)候用普通的Unicode,第二次出現(xiàn)的“容易。”則可以用(距離、長(zhǎng)度)表示,距離的意思是接下來(lái)的字符離前面重復(fù)的字符隔了幾個(gè),長(zhǎng)度則表示有幾個(gè)重復(fù)字符,上面的例子的第二個(gè)“容易。”就表示為(5,3),就是距離為5個(gè)字符,長(zhǎng)度是3,在解壓縮的時(shí)候,解到這個(gè)地方的時(shí)候,往前跳5個(gè)字符,把這個(gè)位置的連續(xù)3個(gè)字符拷貝過(guò)來(lái)就完成了解壓縮,這實(shí)際上不就是指針的概念?沒(méi)有錯(cuò),跟指針很類(lèi)似,不過(guò)在數(shù)據(jù)壓縮領(lǐng)域,一般稱(chēng)為字典編碼,為什么叫字典呢,當(dāng)我們?nèi)ゲ橐粋€(gè)字的時(shí)候,我們總是先去目錄查找這個(gè)字在哪一頁(yè),再翻到那一頁(yè)去看,指針不也是這樣,指針不就是內(nèi)存的地址,要對(duì)一個(gè)內(nèi)存進(jìn)行操作,我們先拿到指針,然后去那塊內(nèi)存去操作。所謂的指針、字典、索引、目錄等等術(shù)語(yǔ),不同的背景可能稱(chēng)呼不同,但我們要理解他們的本質(zhì)。如果使用(5,3)這種表示方法,原來(lái)需要用6個(gè)字節(jié)表示,現(xiàn)在只需要記錄5和3即可。那么,5和3怎么記錄呢?一種方法自然還是可以用Unicode,那么就相當(dāng)于節(jié)省了2個(gè)字節(jié),但是有兩個(gè)問(wèn)題,第一個(gè)問(wèn)題是解壓縮的時(shí)候怎么知道是正常的5和3這兩個(gè)字符,還是這只是一個(gè)特殊標(biāo)記呢?所以前面還得加一個(gè)標(biāo)志來(lái)區(qū)分一下,到底接下來(lái)的Unicode碼是指普通字符,還是指距離和長(zhǎng)度,如果是普通Unicode,則直接查Unicode碼表,如果是距離和長(zhǎng)度,則往前面移動(dòng)一段距離,拷貝即可。第二個(gè)問(wèn)題,還是壓縮程度不行,這么一弄,感覺(jué)壓縮不了多少,如果重復(fù)字符比較長(zhǎng)那倒是比較劃算,因?yàn)榉凑熬嚯x+長(zhǎng)度”就夠了,但比如這個(gè)例子,如果5和3前面加一個(gè)特殊字節(jié),豈不是又是3個(gè)字節(jié),那還不如不壓縮。咋辦呢?能不能對(duì)(5,3)這種整數(shù)進(jìn)行再次壓縮?這里就利用了我們前面說(shuō)的一個(gè)基本原則:出現(xiàn)的少的整數(shù)多編一些比特,出現(xiàn)的多的整數(shù)少編一些比特。那么,比如3、4、5、6、7、8、9這些距離誰(shuí)出現(xiàn)得多?誰(shuí)出現(xiàn)的少呢?誰(shuí)知道?

壓縮之前當(dāng)然不知道,不過(guò)掃描一遍不就知道了?比如,后面那個(gè)重復(fù)的字符串“容易。”按照前面的規(guī)則可以表示為(7,3),即離前面重復(fù)的字符串距離為7,長(zhǎng)度為3。(7,3)指著前面跟自己一樣那個(gè)字符串。那么,為什么不指著第一個(gè)“容易。”要指著第二個(gè)“容易。”呢?如果指著第一個(gè),那就不是(7,3)了,就是(12,3)了。當(dāng)然,表示為(12,3)也可以解壓縮,但是有一個(gè)問(wèn)題,就是12這個(gè)值比7大,大了又怎么了?我們?cè)谏钪袝?huì)發(fā)現(xiàn)一些普遍規(guī)律,重復(fù)現(xiàn)象往往具有局部性。比如,你跟一個(gè)人說(shuō)話(huà),你說(shuō)了一句話(huà)以后,往往很快會(huì)重復(fù)一遍,但是你不會(huì)隔了5個(gè)小時(shí)又重復(fù)這句話(huà),這種特點(diǎn)在文件里面也存在著,到處都是這種例子,比如你在編程的時(shí)候,你定義了一個(gè)變量int nCount,這個(gè)nCount一般你很快就會(huì)用到,不會(huì)離得很遠(yuǎn)。我們前面所說(shuō)的距離代表了你隔了多久再說(shuō)這句話(huà),這個(gè)距離一般不大,既然如此,應(yīng)該以離當(dāng)前字符串距離最近的那個(gè)作為記錄的依據(jù)(也就是指向離自己最近那個(gè)重復(fù)字符串),這樣的話(huà),所有的標(biāo)記都是一些短距離,比如都是3、4、5、6、7而不會(huì)是3、5、78、965等等,如果大多數(shù)都是一些短距離,那么這些短距離就可以用短一些的比特表示,長(zhǎng)一些的距離不太常見(jiàn),則用一些長(zhǎng)一些的比特表示。這樣, 總體的表示長(zhǎng)度就會(huì)減少。好了,我們前面得到了(5,3)、(7、3)這種記錄重復(fù)的表示,距離有兩種:5、7;長(zhǎng)度只有1種:3。咋編碼?越短越好。

既然表示的比特越短越好,3表示為0、5表示為10、7表示為11,行不行?這樣(5,3),(7,3)就只需要表示為100、110,這樣豈不是很短?貌似可以,貌似很高效。

但解壓縮遇到10這兩個(gè)比特的時(shí)候,怎么知道10表示5呢?這種表示方法是一個(gè)映射表,稱(chēng)為碼表。我們?cè)O(shè)計(jì)的上面這個(gè)例子的碼表如下:

3-->0

5-->10

7-->11

這個(gè)碼表也得傳過(guò)去或者記錄在壓縮文件里才行啊,否則無(wú)法解壓縮,但會(huì)不會(huì)記錄了碼表以后整體空間又變大了,會(huì)不會(huì)起不到壓縮的作用?而且一個(gè)碼表怎么記錄?碼表記錄下來(lái)也是一堆數(shù)據(jù),是不是也需要編碼?碼表是否可以繼續(xù)壓縮?那豈不是又需要新的碼表?壓縮會(huì)不會(huì)是一個(gè)永無(wú)止境的過(guò)程?作為一個(gè)入門(mén)級(jí)的同學(xué),大概想到這兒就不容易想下去了。

?

3、ZIP中的LZ編碼思想

上面我們說(shuō)的重復(fù)字符串用指針標(biāo)記記錄下來(lái),這種方法就是LZ這兩個(gè)人提出來(lái)的,理解起來(lái)比較簡(jiǎn)單。后面分析(5,3)這種指針標(biāo)記應(yīng)該怎么編碼的時(shí)候,就涉及到一種非常廣泛的編碼方式,Huffman編碼,Huffman大致和香農(nóng)是一個(gè)時(shí)代的人,這種編碼方式是他在MIT讀書(shū)的時(shí)候提出來(lái)的。接下來(lái),我們來(lái)看看ZIP是怎么做的。

以上面的例子,一個(gè)很簡(jiǎn)單的示意圖如下:

可以看出,ZIP中使用的LZ77算法和前面分析的類(lèi)似,當(dāng)然,如果仔細(xì)對(duì)比的話(huà),ZIP中使用的算法和LZ提出來(lái)的LZ77算法其實(shí)還是有差異的,不過(guò)我建議不用仔細(xì)去扣里面的差異,思想基本是相同的,我們后面會(huì)簡(jiǎn)要分析一下兩者的差異。LZ77算法一般稱(chēng)為“滑動(dòng)窗口壓縮”,我們前面說(shuō)過(guò),該算法的核心是在前面的歷史數(shù)據(jù)中尋找重復(fù)字符串,但如果要壓縮的文件有100MB,是不是從文件頭開(kāi)始找?不是,這里就涉及前面提過(guò)的一個(gè)規(guī)律,重復(fù)現(xiàn)象是具有局部性的,它的基本假設(shè)是,如果一個(gè)字符串要重復(fù),那么也是在附近重復(fù),遠(yuǎn)的地方就不用找了,因此設(shè)置了一個(gè)滑動(dòng)窗口,ZIP中設(shè)置的滑動(dòng)窗口是32KB,那么就是往前面32KB的數(shù)據(jù)中去找,這個(gè)32KB隨著編碼不斷進(jìn)行而往前滑動(dòng)。當(dāng)然,理論上講,把滑動(dòng)窗口設(shè)置得很大,那樣就有更大的概率找到重復(fù)的字符串,壓縮率不就更高了?初看起來(lái)如此,找的范圍越大,重復(fù)概率越大,不過(guò)仔細(xì)想想,可能會(huì)有問(wèn)題,一方面,找的范圍越大,計(jì)算量會(huì)增大,不顧一切地增大滑動(dòng)窗口,甚至不設(shè)置滑動(dòng)窗口,那樣的軟件可能不可用,你想想,現(xiàn)在這種方式,我們?cè)趬嚎s一個(gè)大文件的時(shí)候,速度都已經(jīng)很慢了,如果增大滑動(dòng)窗口,速度就更慢,從工程實(shí)現(xiàn)角度來(lái)說(shuō),設(shè)置滑動(dòng)窗口是必須的;另一方面,找的范圍越大,距離越遠(yuǎn),出現(xiàn)的距離很多,也不利于對(duì)距離進(jìn)行進(jìn)一步壓縮吧,我們前面說(shuō)過(guò),距離和長(zhǎng)度最好出現(xiàn)的值越少越好,那樣更好壓縮,如果出現(xiàn)的很多,如何記錄距離和長(zhǎng)度可能也存在問(wèn)題。不過(guò),我相信滑動(dòng)窗口設(shè)置得越大,最終的結(jié)果應(yīng)該越好一些,不過(guò)應(yīng)該不會(huì)起到特別大的作用,比如壓縮率提高了5%,但計(jì)算量增加了10倍,這顯然有些得不償失。

在第一個(gè)圖中,“容易。”是一個(gè)重復(fù)字符串,距離distance=5,字符串長(zhǎng)度length=3。當(dāng)對(duì)這三個(gè)字符壓縮完畢后,接下來(lái)滑動(dòng)窗口向前移動(dòng)3個(gè)字符,要壓縮的是“我...”這個(gè)字符串,但這個(gè)串在滑動(dòng)窗口內(nèi)沒(méi)找到,所以無(wú)法使用distance+length的方式記錄。這種結(jié)果稱(chēng)為literal。literal的中文含義是原義的意思,表示沒(méi)有使用distance+length的方式記錄的那些普通字符。literal是不是就用原始的編碼方式,比如Unicode方式表示?ZIP里不是這么做的,ZIP把literal認(rèn)為也是一個(gè)數(shù),盡管不能用distance+length表示,但不代表不可以繼續(xù)壓縮。另外,如果“我”出現(xiàn)在了滑動(dòng)窗口內(nèi),是不是就可以用distance+length的方式表示?也不是,因?yàn)橐粋€(gè)字出現(xiàn)重復(fù),不值得用這種方式表示,兩個(gè)字呢?distance+length就是兩個(gè)整數(shù),看起來(lái)也不一定值得,ZIP中確實(shí)認(rèn)為2個(gè)字節(jié)如果在滑動(dòng)窗口內(nèi)找到重復(fù),也不管,只有3個(gè)字節(jié)以上的重復(fù)字符串,才會(huì)用distance+length表示,重復(fù)字符串的長(zhǎng)度越長(zhǎng)越好,因?yàn)椴还芏嚅L(zhǎng),都用distance+length表示就行了。

這樣的話(huà),一段字符串最終就可以表示成literal、distance+length這兩種形式了。LZ系列算法的作用到此為止,下面,Phil Katz考慮使用Huffman對(duì)上面的這些LZ壓縮后的結(jié)果進(jìn)行二次壓縮。個(gè)人認(rèn)為接下來(lái)的過(guò)程才是ZIP的核心,所以我們要熟悉一下Huffman編碼。

?

4、ZIP中的Huffman編碼思想

上面LZ壓縮結(jié)果有三類(lèi)(literal、distance、length),我們拿出distance一類(lèi)來(lái)舉例。distance代表重復(fù)字符串離前一個(gè)一模一樣的字符串之間的距離,是一個(gè)大于0的整數(shù)。如何對(duì)一個(gè)整數(shù)進(jìn)行編碼呢?一種方法是直接用固定長(zhǎng)度表示,比如采用計(jì)算機(jī)里面描述一個(gè)4字節(jié)整數(shù)那樣去記錄,這也是可以的,主要問(wèn)題當(dāng)然是浪費(fèi)存儲(chǔ)空間,在ZIP中,distance這個(gè)數(shù)表示的是重復(fù)字符串之間的距離,顯然,一般而言,越小的距離,出現(xiàn)的數(shù)量可能越多,而越大的距離,出現(xiàn)的數(shù)量可能越少,那么,按照我們前面所說(shuō)的原則,小的值就用較少比特表示,大的值就用較多比特表示,在我們這個(gè)場(chǎng)景里,distance當(dāng)然也不會(huì)無(wú)限大,比如不會(huì)超過(guò)滑動(dòng)窗口的最大長(zhǎng)度,假如對(duì)一個(gè)文件進(jìn)行LZ壓縮后,得到的distance值為:

3、6、4、3、4、3、4、3、5

這個(gè)例子里,3出現(xiàn)了4次,4出現(xiàn)了3次,5出現(xiàn)了1次,6出現(xiàn)了1次。當(dāng)然,不同的文件得到的結(jié)果不同,這里只是舉一個(gè)典型的例子,因?yàn)橹挥?種值,所以我們沒(méi)有必要對(duì)其它整數(shù)編碼。只需要對(duì)這4個(gè)整數(shù)進(jìn)行編碼即可。

那么,怎么設(shè)計(jì)一個(gè)算法,符合3的編碼長(zhǎng)度最短?6的編碼長(zhǎng)度最長(zhǎng)這種直觀(guān)上可行的原則(我們并沒(méi)有說(shuō)這是理論上最優(yōu)的方式)呢?

看起來(lái)似乎很難想出來(lái)。我們先來(lái)簡(jiǎn)化一下,用固定長(zhǎng)度表示。這里有4個(gè)整數(shù),只要使用2個(gè)比特表示即可。于是這樣表示就很簡(jiǎn)單:

00-->3; 01-->4; 10-->5;? 11-->6。

00、01這種編碼結(jié)果稱(chēng)為碼字,碼字的平均長(zhǎng)度是2。上面這個(gè)對(duì)應(yīng)關(guān)系即為碼表,在壓縮時(shí),需要將碼表放在最前面,后面的數(shù)字就用碼字表示,解碼時(shí),先把碼表記錄在內(nèi)存里,比如用一個(gè)哈希表記錄下來(lái),解壓縮時(shí),遇到00,就解碼為3等等。

因?yàn)槌霈F(xiàn)了9個(gè)數(shù),所以全部碼字總長(zhǎng)度為18個(gè)比特。(我們暫時(shí)不考慮記錄碼表到底要占多少空間)

想要編碼結(jié)果更短,因?yàn)?出現(xiàn)的最多,所以考慮把3的碼字縮短點(diǎn),比如3是不是可以用1個(gè)比特表示,這樣才算縮短吧,因?yàn)?和1只是二進(jìn)制的一個(gè)標(biāo)志,所以用0還是1沒(méi)有本質(zhì)區(qū)別,那么,我們暫定把3用比特0表示。那么,4、5、6還能用0開(kāi)頭的碼字表示呢?

這樣會(huì)存在問(wèn)題,因?yàn)?、5、6的編碼結(jié)果如果以0開(kāi)頭,那么,在解壓縮的時(shí)候,遇到比特0,就不知道是表示3還是表示4、5、6了,就無(wú)法解碼,當(dāng)然,似乎理論上也不是不可以,比如可以往后解解看,比如假定0表示3的條件下往后解,如果無(wú)效則說(shuō)明這個(gè)假設(shè)不對(duì),但這種方式很容易出現(xiàn)兩個(gè)字符串的編碼結(jié)果是一樣的,這個(gè)誰(shuí)來(lái)保證?所以,4、5、6都得以1開(kāi)頭才行,那么,按照這個(gè)原則,4用1個(gè)比特也不行,因?yàn)?、6要么以0開(kāi)頭,要么以1開(kāi)頭,就無(wú)法編碼了,所以我們將4的碼字增加至2個(gè)比特,比如10,于是我們得到了部分碼表:

0-->3;10-->4。

按照這個(gè)道理,5、6既不能以0開(kāi)頭,也不能以10開(kāi)頭了,因?yàn)橥瑯哟嬖跓o(wú)法解碼的問(wèn)題,所以5應(yīng)該以11開(kāi)頭,就定為11行不行呢?也不行,因?yàn)?就不知道怎么編碼了,6也不能以0開(kāi)頭,也不能以10、11開(kāi)頭,那就無(wú)法表示了,所以,迫不得已,我們必須把5擴(kuò)展一位,比如110,那么,6顯然就可以用111表示了,反正也沒(méi)有其他數(shù)了。于是我們得到了最終的碼表:

0-->3;10-->4;110-->5;111-->6。

看起來(lái),編碼結(jié)果只能是這樣了,我們來(lái)算一下,碼字的總長(zhǎng)度減少了沒(méi)有,原來(lái)的9個(gè)數(shù)是3、6、4、3、4、3、4、3、5,分別對(duì)應(yīng)的碼字是:

0、111、10、0、10、0、10、0、110

算一下,總共16個(gè)比特,果然比前面那種方式變短了。我們?cè)谇懊娴脑O(shè)計(jì)過(guò)程中,是按照這些值出現(xiàn)次數(shù)由高到底的順序去找碼字的,比如先確定3,再確定4、5、6等等。按照一個(gè)碼字不能是另一個(gè)碼字的前綴這一規(guī)則,逐步獲得所有的碼字。這種編碼規(guī)則有一個(gè)專(zhuān)用術(shù)語(yǔ),稱(chēng)為前綴碼。Huffman編碼基本上就是這么做的,把出現(xiàn)頻率排個(gè)序,然后逐個(gè)去找,這個(gè)逐個(gè)去找的過(guò)程,就引入了二叉樹(shù)。不過(guò)Huffman的算法一般是從頻率由低到高排序,從樹(shù)的下面依次往上合并,不過(guò)本質(zhì)上沒(méi)區(qū)別,理解思想即可。上面的結(jié)果可以用一顆二叉樹(shù)表示為下圖:

這棵樹(shù)也稱(chēng)為碼樹(shù),其實(shí)就是碼表的一種形式化描述,每個(gè)節(jié)點(diǎn)(除了葉子節(jié)點(diǎn))都會(huì)分出兩個(gè)分支,左分支代表比特0,右邊分支代表1,從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)的一個(gè)比特序列就是碼字。因?yàn)橹挥腥~子節(jié)點(diǎn)可以是碼字,所以這樣也符合一個(gè)碼字不能是另一個(gè)碼字的前綴這一原則。說(shuō)到這里,可以說(shuō)一下另一個(gè)話(huà)題,就是一個(gè)映射表map在內(nèi)存中怎么存儲(chǔ),沒(méi)有相關(guān)經(jīng)驗(yàn)的可以跳過(guò),map實(shí)現(xiàn)的是key-->value這樣的一個(gè)表,map的存儲(chǔ)一般有哈希表和樹(shù)形存儲(chǔ)兩類(lèi),樹(shù)形存儲(chǔ)就可以采用上面這棵樹(shù),樹(shù)的中間節(jié)點(diǎn)并沒(méi)有什么含義,葉子節(jié)點(diǎn)的值表示value,從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)上分支的值就是key,這樣比較適合存儲(chǔ)那些key由多個(gè)不等長(zhǎng)字符組成的場(chǎng)合,比如key如果是字符串,那么把二叉樹(shù)的分支擴(kuò)展很多,成為多叉樹(shù),每個(gè)分支就是a,b,c,d這種字符,這棵樹(shù)也就是Trie樹(shù),是一種很好使的數(shù)據(jù)結(jié)構(gòu)。利用樹(shù)的遍歷算法,就實(shí)現(xiàn)了一個(gè)有序Map。

好了,我們理解了Huffman編碼的思想,我們來(lái)看看distance的實(shí)際情況。ZIP中滑動(dòng)窗口大小固定為32KB,也就是說(shuō),distance的值范圍是1-32768。那么,通過(guò)上面的方式,統(tǒng)計(jì)頻率后,就得到32768個(gè)碼字,按照上面這種方式可以構(gòu)建出來(lái)。于是我們會(huì)遇到一個(gè)最大的問(wèn)題,那就是這棵樹(shù)太大了,怎么記錄呢?

好了,個(gè)人認(rèn)為到了ZIP的核心了,那就是碼樹(shù)應(yīng)該怎么縮小,以及碼樹(shù)怎么記錄的問(wèn)題。

?

5、ZIP中Huffman碼樹(shù)的記錄方式

分析上面的例子,看看這個(gè)碼表:

0-->3;10-->4;110-->5;111-->6。

我們之前提過(guò),0和1就是二進(jìn)制的一個(gè)標(biāo)志,互換一下其實(shí)根本不影響編碼長(zhǎng)度,所以,下面的碼表其實(shí)是一樣的:

1-->3;00-->4;010-->5;011-->6。

1-->3;01-->4;000-->5;001-->6。

0-->3;11-->4;100-->5;101-->6。

。。。。。

這些都可以,而且編碼長(zhǎng)度完全一樣,只是碼字不同而已。

對(duì)比一下第一個(gè)和第二個(gè)例子,對(duì)應(yīng)的碼樹(shù)是這個(gè)樣子:

也就是說(shuō),我們把碼樹(shù)的任意節(jié)點(diǎn)的左右分支旋轉(zhuǎn)(0、1互換),也可以稱(chēng)為樹(shù)的左右互換,其實(shí)不影響編碼長(zhǎng)度,也就是說(shuō),這些碼表其實(shí)都是一樣好的,使用哪個(gè)都可以。

這個(gè)規(guī)律暗示了什么信息呢?暗示了碼表可以怎么記錄呢?Phil Katz當(dāng)年在牢里也深深地思考了這一問(wèn)題。

為了體會(huì)Phil Katz當(dāng)時(shí)的心情,我們有必要盯著這兩棵樹(shù)思考幾分鐘:怎么把一顆樹(shù)用最少的比特記錄下來(lái)?

Phil Katz當(dāng)時(shí)思考的邏輯我猜是這樣的,既然這些樹(shù)的壓縮程度都一樣,那干脆使用最特殊的那棵樹(shù),反正壓縮程度都一樣,只要ZIP規(guī)定了這棵樹(shù)的特殊性,那么我記錄的信息就可以最少,這種特殊化的思想在后面還會(huì)看到。不同的樹(shù)當(dāng)然有不同的特點(diǎn),比如數(shù)據(jù)結(jié)構(gòu)里面常見(jiàn)的平衡樹(shù)也是一類(lèi)特殊的樹(shù),他選的樹(shù)就是左邊那棵,這棵樹(shù)有一個(gè)特點(diǎn),越靠左邊越淺,越往右邊越深,是這些樹(shù)中最不平衡的樹(shù)。ZIP里的壓縮算法稱(chēng)為Deflate算法,這棵樹(shù)也稱(chēng)為Deflate樹(shù),對(duì)應(yīng)的解壓縮算法稱(chēng)為Inflate,Deflate的大致意思是把輪胎放氣了,意為壓縮;Inflate是給輪胎打氣的意思,意為解壓。那么,Deflate樹(shù)的特殊性又帶來(lái)什么了?

揭曉答案吧,Phil Katz認(rèn)為換來(lái)?yè)Q去只有碼字長(zhǎng)度不變,如果規(guī)定了一類(lèi)特殊的樹(shù),那么就只需要記錄碼字長(zhǎng)度即可。比如,一個(gè)有效的碼表是0-->3;10-->4;110-->5;111-->6。但只需要記錄這個(gè)對(duì)應(yīng)關(guān)系即可:

3  4  5  6

1  2  3  3

也就是說(shuō),把1、2、3、3記錄下來(lái),解壓一邊照著左邊那棵樹(shù)的形狀構(gòu)造一顆樹(shù),然后只需要1、2、3、3這個(gè)信息自然就知道是0、10、110、111。這就是Phil Katz想出來(lái)的ZIP最核心的一點(diǎn):這棵碼樹(shù)用碼字長(zhǎng)度序列記錄下來(lái)即可。

當(dāng)然,只把1、2、3、3這個(gè)序列記錄下來(lái)還不行,比如不知道111對(duì)應(yīng)5還是對(duì)應(yīng)6?

所以,構(gòu)造出樹(shù)來(lái)只是知道了有哪些碼字了,但是這些碼字到底對(duì)應(yīng)哪些整數(shù)還是不知道。

Phil Katz于是又出現(xiàn)了一個(gè)想法:記錄1、2、3、3還是記錄1、3、2、3,或者3、3、2、1,其實(shí)都能構(gòu)造出這棵樹(shù)來(lái),那么,為什么不按照一個(gè)特殊的順序記錄呢?這個(gè)順序就是整數(shù)的大小順序,比如上面的3、4、5、6是整數(shù)大小順序排列的,那么,記錄的順序就是1、2、3、3。而不是2、3、3、1。

好了,根據(jù)1、2、3、3這個(gè)信息構(gòu)造出了碼字,這些碼字對(duì)應(yīng)的整數(shù)一個(gè)比一個(gè)大,假如我們知道編碼前的整數(shù)就是3、4、5、6這四個(gè)數(shù),那就能對(duì)應(yīng)起來(lái)了,不過(guò)到底是哪四個(gè)還是不知道啊?這個(gè)整數(shù)可以表示距離啊,距離不知道怎么去解碼LZ?

Phil Katz又想了,既然distance的范圍是1-32768,那么就按照這個(gè)順序記錄。上面的例子1和2沒(méi)有,那就記錄長(zhǎng)度0。所以記錄下來(lái)的碼字長(zhǎng)度序列為:

0、0、1、2、3、3、0、0、0、0、0、。。。。。。。。。。。。

這樣就知道構(gòu)造出來(lái)的碼字對(duì)應(yīng)哪個(gè)整數(shù)了吧,但因?yàn)閐istance可能的值很多(32768個(gè)),但實(shí)際出現(xiàn)的往往不多,中間會(huì)出現(xiàn)很多0(也就是根本就沒(méi)出現(xiàn)這個(gè)距離),不過(guò)這個(gè)問(wèn)題倒是可以對(duì)連續(xù)的0做個(gè)特殊標(biāo)記,這樣是不是就行了呢?還有什么問(wèn)題?

我們還是要站在時(shí)代的高度來(lái)看待這個(gè)問(wèn)題。我們明白,每個(gè)distance肯定對(duì)應(yīng)唯一一個(gè)碼字,使用Huffman編碼可以得到所有碼字,但是因?yàn)閐istance可能非常多,雖然一般不會(huì)有32768這么多,但對(duì)一個(gè)大些的文件進(jìn)行LZ編碼,distance上千還是很正常的,所以這棵樹(shù)很大,計(jì)算量、消耗的內(nèi)存都容易超越了那個(gè)時(shí)代的硬件條件,那么怎么辦呢?這里再次體現(xiàn)了Phil Katz對(duì)Huffman編碼掌握的深度,他把distance劃分成多個(gè)區(qū)間,每個(gè)區(qū)間當(dāng)做一個(gè)整數(shù)來(lái)看,這個(gè)整數(shù)稱(chēng)為Distance Code。當(dāng)一個(gè)distance落到某個(gè)區(qū)間,則相當(dāng)于是出現(xiàn)了那個(gè)Code,多個(gè)distance對(duì)應(yīng)于一個(gè)Distance Code,Distance雖然很多,但Distance Code可以劃分得很少,只要我們對(duì)Code進(jìn)行Huffman編碼,得到Code的編碼后,Distance Code再根據(jù)一定規(guī)則擴(kuò)展出來(lái)。那么,劃分多少個(gè)區(qū)間?怎么劃分區(qū)間呢?我們分析過(guò),越小的距離,出現(xiàn)的越多;越大的距離,出現(xiàn)的越少,所以這種區(qū)間劃分不是等間隔的,而是越來(lái)越稀疏的,類(lèi)似于下面的劃分:

1、2、3、4這四個(gè)特殊distance不劃分,或者說(shuō)1個(gè)Distance就是1個(gè)區(qū)間;5,6作為一個(gè)區(qū)間;7、8作為一個(gè)區(qū)間等等,基本上,區(qū)間的大小都是1、2、4、8、16、32這么遞增的,越往后,涵蓋的距離越多。為什么這么分呢?首先自然是距離越小出現(xiàn)頻率越高,所以距離值小的時(shí)候,劃分密一些,這樣相當(dāng)于一個(gè)放大鏡,可以對(duì)小的距離進(jìn)行更精細(xì)地編碼,使得其編碼長(zhǎng)度與其出現(xiàn)次數(shù)盡量匹配;對(duì)于距離較大那些,因?yàn)槌霈F(xiàn)頻率低,所以可以適當(dāng)放寬一些。另一個(gè)原因是,只要知道這個(gè)區(qū)間Code的碼字,那么對(duì)于這個(gè)區(qū)間里面的所有distance,后面追加相應(yīng)的多個(gè)比特即可,比如,17-24這個(gè)區(qū)間的Huffman碼字是110,因?yàn)?7-24這個(gè)區(qū)間有8個(gè)整數(shù),于是按照下面的規(guī)則即可獲得其distance對(duì)應(yīng)的碼字:

17-->110 000

18-->110 001

19-->110 010

20-->110 011

21-->110 100

22-->110 101

23-->110 110

24-->110 111

這樣計(jì)算復(fù)雜度和內(nèi)存消耗是不是很小了,因?yàn)樾枰M(jìn)行Huffman編碼的整數(shù)一下字變少了,這棵樹(shù)不會(huì)多大,計(jì)算起來(lái)時(shí)間和空間復(fù)雜度降低,擴(kuò)展起來(lái)也比較簡(jiǎn)單。當(dāng)然,從理論上來(lái)說(shuō),這樣的編碼方式實(shí)際上將編碼過(guò)程分為了兩級(jí),并不是理論上最優(yōu)的,把所有distance當(dāng)作一個(gè)大空間去編碼才可能得到最優(yōu)結(jié)果,不過(guò)還是那句話(huà),工程實(shí)現(xiàn)的限制,在壓縮軟件實(shí)現(xiàn)上,我們不能用壓縮率作為衡量一個(gè)算法優(yōu)劣的唯一指標(biāo),其實(shí)耗費(fèi)的時(shí)間和空間同樣是指標(biāo),所以需要看綜合指標(biāo)。很多其他軟件也一樣,擴(kuò)展性、時(shí)間空間復(fù)雜度、穩(wěn)定性、移植性、維護(hù)的方便性等等是工程上很重要的東西。我沒(méi)有看過(guò)RAR是如何壓縮的,有可能是在類(lèi)似的地方進(jìn)行了改進(jìn),如果如此,那也是站在巨人的肩膀上,而且硬件條件不同,進(jìn)行一些改進(jìn)也并不奇怪。

具體來(lái)說(shuō),Phil Katz把distance劃分為30個(gè)區(qū)間,如下圖:

這個(gè)圖是我從David Salomon的《Data Compression The Complete Reference》這本書(shū)(第四版)中拷貝出來(lái)的,下面的有些圖也是,如果需要對(duì)數(shù)據(jù)壓縮進(jìn)行全面的了解,這本書(shū)幾乎是最全的了,強(qiáng)烈推薦。

當(dāng)然,你要問(wèn)為什么是30個(gè)區(qū)間,我也沒(méi)分析過(guò),也許是復(fù)雜度和壓縮率經(jīng)過(guò)試驗(yàn)之后的一種折中吧。

其中,左邊的Code表示區(qū)間的編號(hào),是0-29,共30個(gè)區(qū)間,這只是個(gè)編號(hào),沒(méi)有特別的含義,但Huffman就是對(duì)0-29這30個(gè)Code進(jìn)行編碼的,得到區(qū)間的碼字;

bits表示distance的碼字需要在Code的碼字基礎(chǔ)上擴(kuò)展幾位,比如0就表示不擴(kuò)展,最大的13表示要擴(kuò)展13位,因此,最大的區(qū)間包含的distance數(shù)量為8192個(gè)。

Distance一列則表示這個(gè)區(qū)間涵蓋的distance范圍。

理解了碼樹(shù)如何有效記錄,以及如何縮小碼樹(shù)的過(guò)程,我覺(jué)得就理解了ZIP的精髓。

?

6、ZIP中l(wèi)iteral和length的壓縮方式

說(shuō)完了distance,LZ編碼結(jié)果還有兩類(lèi):literal和length。這兩類(lèi)也利用了類(lèi)似于distance的方式進(jìn)行壓縮。

前面分析過(guò),literal表示未匹配的字符,我們前面之所以拿漢字來(lái)舉例,完全是為了便于理解,ZIP之所以是通用壓縮,它實(shí)際上是針對(duì)字節(jié)作為基本字符來(lái)編碼的,所以一個(gè)literal至多有256種可能。

length表示重復(fù)字符串長(zhǎng)度,length=1當(dāng)然不會(huì)出現(xiàn),因?yàn)橐粋€(gè)字符不值得用distance+length去記錄,重復(fù)字符串當(dāng)然越長(zhǎng)越好,Phil Katz(下面還是簡(jiǎn)稱(chēng)PK了,拷貝太麻煩)認(rèn)為,length=2也不值得用這種方式記錄,還是太短了,所以PK把length最小值認(rèn)為是3,必須3個(gè)以上字符的字符串出現(xiàn)重復(fù)才用distance+length記錄。那么,最大的length是多少呢?理論上當(dāng)然可以很長(zhǎng)很長(zhǎng),比如一個(gè)文件就是連續(xù)的0,這個(gè)重復(fù)字符串長(zhǎng)度其實(shí)接近于這個(gè)文件的實(shí)際長(zhǎng)度。但是PK把length的范圍做了限制,限定length的個(gè)數(shù)跟literal一樣,也只有256個(gè),因?yàn)镻K認(rèn)為,一個(gè)重復(fù)字符串達(dá)到了256個(gè)已經(jīng)很長(zhǎng)了,概率非常小;另外,其實(shí)哪怕超過(guò)了256,我還是認(rèn)為是一段256再加上另外一段,增加一個(gè)distance+length就行了嘛,并不影響結(jié)果。而且這樣做,我想同樣也考慮了硬件條件吧。

初看有點(diǎn)奇怪的在于,將literal和length二者合二為一,什么意思呢?就是對(duì)這兩種整數(shù)(literal本質(zhì)上是一個(gè)字節(jié))共用一個(gè)Huffman碼表,一會(huì)兒解釋為什么。PK對(duì)Huffman的理解我覺(jué)得達(dá)到了爐火純青的地步,前面已經(jīng)看到,后面還會(huì)看到。他認(rèn)為Huffman編碼的輸入反正說(shuō)白了就是一個(gè)集合的元素就行,無(wú)論這個(gè)元素是啥,所以多個(gè)集合看做一個(gè)集合當(dāng)作Huffman編碼的輸入沒(méi)啥問(wèn)題。literal用整數(shù)0-255表示,256是一個(gè)結(jié)束標(biāo)志,解碼以后結(jié)果是256表示解碼結(jié)束;從257開(kāi)始表示length,所以257這個(gè)數(shù)表示length=3,258這個(gè)數(shù)表示length=4等等,但PK也不是一直這么一一對(duì)應(yīng),和distance一樣,也是把length(總共256個(gè)值)劃分為29個(gè)區(qū)間,其結(jié)果如下圖:

其中的含義和distance類(lèi)似,不再贅述,所以literal/length這個(gè)Huffman編碼的輸入元素一共285個(gè),其中256表示解碼結(jié)束標(biāo)志。為什么要把二者合二為一呢?因?yàn)楫?dāng)解碼器接收到一個(gè)比特流的時(shí)候,首先可以按照l(shuí)iteral/length這個(gè)碼表來(lái)解碼,如果解出來(lái)是0-255,就表示未匹配字符,如果是256,那自然就結(jié)束,如果是257-285之間,則表示length,把后面擴(kuò)展比特加上形成length后,后面的比特流肯定就表示distance,因此,實(shí)際上通過(guò)一個(gè)Huffman碼表,對(duì)各類(lèi)情況進(jìn)行了統(tǒng)一,而不是通過(guò)加一個(gè)什么標(biāo)志來(lái)區(qū)分到底是literal還是重復(fù)字符串。

好了,理解了上面的過(guò)程,就理解了ZIP壓縮的第二步,第一步是LZ編碼,第二步是對(duì)LZ編碼后結(jié)果(literal、distance、length)進(jìn)行的再編碼,因?yàn)閘iteral/length是一個(gè)碼表,我稱(chēng)其為Huffman碼表1,distance那個(gè)碼表稱(chēng)為Huffman碼表2。前面我們已經(jīng)分析了,Huffman碼樹(shù)用一個(gè)碼字長(zhǎng)度序列表示,稱(chēng)為CL(Code Length),記錄兩個(gè)碼表的碼字長(zhǎng)度序列分別記為CL1、CL2。碼樹(shù)記錄下來(lái),對(duì)literal/length的編碼比特流稱(chēng)為L(zhǎng)IT比特流;對(duì)distance的編碼比特流稱(chēng)為DIST比特流。

按照上面的方法,LZ的編碼結(jié)果就變成四塊:CL1、CL2、LIT比特流、DIST比特流。CL1、CL2是碼字長(zhǎng)度的序列,這個(gè)序列說(shuō)白了就是一堆正整數(shù),因此,PK繼續(xù)深挖,認(rèn)為這個(gè)序列還應(yīng)該繼續(xù)壓縮,也就是說(shuō),對(duì)碼表進(jìn)行壓縮。

?

7、ZIP中對(duì)CL進(jìn)行再次壓縮的方法

這里仍然沿用Huffman的想法,因?yàn)镃L也是一堆整數(shù),那么當(dāng)然可以再次應(yīng)用Huffman編碼。不過(guò)在這之前,PK對(duì)CL序列進(jìn)行了一點(diǎn)處理。這個(gè)處理也是很精巧的。

CL序列表示一系列整數(shù)對(duì)應(yīng)的碼字長(zhǎng)度,對(duì)于literal/length來(lái)說(shuō),總共有0-285這么多符號(hào),所以這個(gè)序列長(zhǎng)度為286,每個(gè)符號(hào)都有一個(gè)碼字長(zhǎng)度,當(dāng)然,這里面可能會(huì)出現(xiàn)大段連續(xù)的0,因?yàn)槟承┳址蜷L(zhǎng)度不存在,尤其是對(duì)英文文本編碼的時(shí)候,非ASCII字符就根本不會(huì)出現(xiàn),length較大的值出現(xiàn)概率也很小,所以出現(xiàn)大段的0是很正常的;對(duì)于distance也類(lèi)似,也可能出現(xiàn)大段的0。PK于是先進(jìn)行了一下游程編碼。在說(shuō)什么是游程編碼之前,我們談?wù)凱K對(duì)CL序列的認(rèn)識(shí)。

literal/length的編碼符號(hào)總共286個(gè)(回憶:256個(gè)Literal+1個(gè)結(jié)束標(biāo)志+29個(gè)length區(qū)間),distance的編碼符號(hào)總共30個(gè)(回憶:30個(gè)區(qū)間),所以這顆碼樹(shù)不會(huì)特別深,Huffman編碼后的碼字長(zhǎng)度不會(huì)特別長(zhǎng),PK認(rèn)為最長(zhǎng)不會(huì)超過(guò)15,也就是樹(shù)的深度不會(huì)超過(guò)15,這個(gè)是否是理論證明我還沒(méi)有分析,有興趣的同學(xué)可以分析一下。因此,CL1和CL2這兩個(gè)序列的任意整數(shù)值的范圍是0-15。0表示某個(gè)整數(shù)沒(méi)有出現(xiàn)(比如literal=0x12, length Code=8, distance Code=15等等)。

什么叫游程呢?就是一段完全相同的數(shù)的序列。什么叫游程編碼呢?說(shuō)起來(lái)原理更簡(jiǎn)單,就是對(duì)一段連續(xù)相同的數(shù),記錄這個(gè)數(shù)一次,緊接著記錄出現(xiàn)了多少個(gè)即可。David的書(shū)中舉了這個(gè)例子,比如CL序列如下:

4, 4, 4, 4, 4, 3, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2
那么,游程編碼的結(jié)果為:

4, 16, 01(二進(jìn)制), 3, 3, 3, 6, 16, 11(二進(jìn)制), 16, 00(二進(jìn)制), 17,011(二進(jìn)制), 2, 16, 00(二進(jìn)制)
這是什么意思呢?因?yàn)镃L的范圍是0-15,PK認(rèn)為重復(fù)出現(xiàn)2次太短就不用游程編碼了,所以游程長(zhǎng)度從3開(kāi)始。用16這個(gè)特殊的數(shù)表示重復(fù)出現(xiàn)3、4、5、6個(gè)這樣一個(gè)游程,分別后面跟著00、01、10、11表示(實(shí)際存儲(chǔ)的時(shí)候需要低比特優(yōu)先存儲(chǔ),需要把比特倒序來(lái)存,博文的一些例子有時(shí)候會(huì)忽略這點(diǎn),實(shí)際寫(xiě)程序的時(shí)候一定要注意,否則會(huì)得到錯(cuò)誤結(jié)果)。于是4,4,4,4,4,這段游程記錄為4,16,01,也就是說(shuō),4這個(gè)數(shù),后面還會(huì)連續(xù)出現(xiàn)了4次。6,16,11,16,00表示6后面還連續(xù)跟著6個(gè)6,再跟著3個(gè)6;因?yàn)檫B續(xù)的0出現(xiàn)的可能很多,所以用17、18這兩個(gè)特殊的數(shù)專(zhuān)門(mén)表示0游程,17后面跟著3個(gè)比特分別記錄長(zhǎng)度為3-10(總共8種可能)的游程;18后面跟著7個(gè)比特表示11-138(總共128種可能)的游程。17,011(二進(jìn)制)表示連續(xù)出現(xiàn)6個(gè)0;18,0111110(二進(jìn)制)表示連續(xù)出現(xiàn)62個(gè)0。總之記住,0-15是CL可能出現(xiàn)的值,16表示除了0以外的其它游程;17、18表示0游程。因?yàn)槎M(jìn)制實(shí)際上也是個(gè)整數(shù),所以上面的序列用整數(shù)表示為:

4, 16, 1, 3, 3, 3, 6, 16, 3, 16, 0, 17, 3, 2, 16, 0

我們又看到了一串整數(shù),這串整數(shù)的值的范圍是0-18。這個(gè)序列稱(chēng)為SQ(Sequence的意思)。因?yàn)橛袃蓚€(gè)CL1、CL2,所以對(duì)應(yīng)的有兩個(gè)SQ1、SQ2。

針對(duì)SQ1、SQ2,PK用了第三個(gè)Huffman碼表來(lái)對(duì)這兩個(gè)序列進(jìn)行編碼。通過(guò)統(tǒng)計(jì)各個(gè)整數(shù)(0-18范圍內(nèi))的出現(xiàn)次數(shù),按照相同的思路,對(duì)SQ1和SQ2進(jìn)行了Huffman編碼,得到的碼流記為SQ1 bits和SQ2 bits。同時(shí),這里又需要記錄第三個(gè)碼表,稱(chēng)為Huffman碼表3。同理,這個(gè)碼表也用相同的方法記錄,也等效為一個(gè)碼長(zhǎng)序列,稱(chēng)為CCL,因?yàn)橹炼嘤?-18個(gè),PK認(rèn)為樹(shù)的深度至多為7,于是CCL的范圍是0-7。

當(dāng)?shù)玫搅薈CL序列后,PK決定不再折騰,對(duì)這個(gè)序列用普通的3比特定長(zhǎng)編碼記錄下來(lái)即可,即000代表0,111代表7。但實(shí)際上還有一點(diǎn)小折騰,就是最后這個(gè)序列如果全部記錄,那就需要19*3=57個(gè)比特,PK認(rèn)為CL序列里面CL范圍為0-15,特殊的幾個(gè)值是16、17、18,如果把CCL序列位置置換一下,把16、17、18這些放前面,那么這個(gè)CCL序列就很可能最后面跟著一串0(因?yàn)镃L=14,15這些很可能沒(méi)有),所以最后還引入了一個(gè)置換,其示意圖如下,分別表示置換前的CCL序列和置換后的CCL。可以看出,16、17、18對(duì)應(yīng)的CCL被放到了前面,這樣如果尾部出現(xiàn)了一些0,就只需要記錄CCL長(zhǎng)度即可,后面的0不記錄。可以繼續(xù)節(jié)省一些比特,不過(guò)這個(gè)例子尾部置換后只有1個(gè)0:

不過(guò)粗看起來(lái),這個(gè)置換效果并不好,我一開(kāi)始接觸這個(gè)置換的時(shí)候,我覺(jué)得應(yīng)該按照16、17、18、0、1、2、3、。。。這樣的順序來(lái)存儲(chǔ),如果按照我理解的,那么置換后的結(jié)果如下:

2、4、0、4、5、5、1、5、0、6、0、0、0、0、0、0、0、0、0

這樣后面的一大串0直接截?cái)?#xff0c;比PK的方法更短。但PK卻按照上面的順序。我總是認(rèn)為,我覺(jué)得牛人可能出錯(cuò)了的時(shí)候,往往是我自己錯(cuò)了,所以我又仔細(xì)想了一下,上面的順序特點(diǎn)比較明顯,直觀(guān)上看,PK認(rèn)為CL為0和中間的值出現(xiàn)得比較多(放在了前面),但CL比較小的和比較大的出現(xiàn)得比較少(1、15、2、14這些放在了后面,你看,后面交叉著放),在文件比較小的時(shí)候,這種方法效果不算好,上面就是一個(gè)典型的例子,但文件比較大了以后,CL1、CL2碼樹(shù)比較大,碼字長(zhǎng)度普遍比較長(zhǎng),大部分很可能接近于中間值,那么這個(gè)時(shí)候PK的方法可能就體現(xiàn)出優(yōu)勢(shì)了。不得不說(shuō),對(duì)一個(gè)算法或者數(shù)據(jù)結(jié)構(gòu)的優(yōu)化程度,簡(jiǎn)直完全取決于程序員對(duì)那個(gè)東西細(xì)節(jié)的理解的深度。當(dāng)我仔細(xì)研究了ZIP壓縮算法的過(guò)程之后,我對(duì)PK這種深夜埋頭冥思苦想的大牛佩服得五體投地。

到此為止,ZIP壓縮算法的結(jié)果已經(jīng)完畢。這個(gè)算法命名為Deflate算法。總結(jié)一下其編碼流程為:

?

8、Deflate壓縮數(shù)據(jù)格式

ZIP的格式實(shí)際上就是Deflate壓縮碼流外面套了一層文件相關(guān)的信息,這里先介紹Deflate壓縮碼流格式。其格式為:

Header:3個(gè)比特,第一個(gè)比特如果是1,表示此部分為最后一個(gè)壓縮數(shù)據(jù)塊;否則表示這是.ZIP文件的某個(gè)中間壓縮數(shù)據(jù)塊,但后面還有其他數(shù)據(jù)塊。這是ZIP中使用分塊壓縮的標(biāo)志之一;第2、3比特表示3個(gè)選擇:壓縮數(shù)據(jù)中沒(méi)有使用Huffman、使用靜態(tài)Huffman、使用動(dòng)態(tài)Huffman,這是對(duì)LZ77編碼后的literal/length/distance進(jìn)行進(jìn)一步編碼的標(biāo)志。我們前面分析的都是動(dòng)態(tài)Huffman,其實(shí)Deflate也支持靜態(tài)Huffman編碼,靜態(tài)Huffman編碼原理更為簡(jiǎn)單,無(wú)需記錄碼表(因?yàn)镻K自己定義了一個(gè)固定的碼表),但壓縮率不高,所以大多數(shù)情況下都是動(dòng)態(tài)Huffman。

HLIT:5比特,記錄literal/length碼樹(shù)中碼長(zhǎng)序列(CL1)個(gè)數(shù)的一個(gè)變量。后面CL1個(gè)數(shù)等于HLIT+257(因?yàn)橹辽儆?-255總共256個(gè)literal,還有一個(gè)256表示解碼結(jié)束,但length的個(gè)數(shù)不定)。

HDIST:5比特,記錄distance碼樹(shù)中碼長(zhǎng)序列(CL2)個(gè)數(shù)的一個(gè)變量。后面CL2個(gè)數(shù)等于HDIST+1。哪怕沒(méi)有1個(gè)重復(fù)字符串,distance都為0也是一個(gè)CL。

HCLEN:4比特,記錄Huffman碼表3中碼長(zhǎng)序列(CCL)個(gè)數(shù)的一個(gè)變量。后面CCL個(gè)數(shù)等于HCLEN+4。PK認(rèn)為CCL個(gè)數(shù)不會(huì)低于4個(gè),即使對(duì)于整個(gè)文件只有1個(gè)字符的情況。

接下來(lái)是3比特編碼的CCL,一共HCLEN+4個(gè),用以構(gòu)造Huffman碼表3;

接下來(lái)是對(duì)CL1(碼長(zhǎng))序列經(jīng)過(guò)游程編碼(SQ1:縮短的整數(shù)序列)后,并對(duì)SQ1繼續(xù)用Huffman編碼后的比特流。包含HLIT+257個(gè)CL1,其解碼碼表為Huffman碼表3,用以構(gòu)造Huffman碼表1;

接下來(lái)是對(duì)CL2(碼長(zhǎng))序列經(jīng)過(guò)游程編碼(SQ2:縮短的整數(shù)序列)后,并對(duì)SQ2繼續(xù)用Huffman編碼后的比特流。包含HDIST+1個(gè)CL2,其解碼碼表為Huffman碼表3,用于構(gòu)造Huffman碼表2;

總之,上面的數(shù)據(jù)都是為了構(gòu)造LZ解碼需要的2個(gè)Huffman碼表。

接下來(lái)才是經(jīng)過(guò)Huffman編碼的壓縮數(shù)據(jù),解碼碼表為Huffman碼表1和碼表2。
最后是數(shù)據(jù)塊結(jié)束標(biāo)志,即literal/length這個(gè)碼表輸入符號(hào)位256的編碼比特。
對(duì)倒數(shù)第1、2內(nèi)容塊進(jìn)行解碼時(shí),首先利用Huffman碼表1進(jìn)行解碼,如果解碼所得整數(shù)位于0-255之間,表示literal未匹配字符,接下來(lái)仍然利用Huffman碼表1解碼;如果位于257-285之間,表示length匹配長(zhǎng)度,之后需要利用Huffman碼表2進(jìn)行解碼得到distance偏移距離;如果等于256,表示數(shù)據(jù)塊解碼結(jié)束。

?

9、ZIP文件格式解析

?上面各節(jié)對(duì)ZIP的原理進(jìn)行了分析,這一節(jié)我們來(lái)看一個(gè)實(shí)際的例子,為了更好地描述,我們盡量把這個(gè)例子舉得簡(jiǎn)單一些。下面是我隨便從一本書(shū)拷貝出來(lái)的一段較短的待壓縮的英文文本數(shù)據(jù):

As mentioned above,there are many kinds of wireless systems other than cellular.

這段英文文本長(zhǎng)度為80字節(jié)。經(jīng)過(guò)ZIP壓縮后,其內(nèi)容如下:

可以看到,第1、2字節(jié)就是PK。看著怎么比原文還長(zhǎng),這怎么叫壓縮?實(shí)際上,這里面大部分內(nèi)容是ZIP的文件標(biāo)記開(kāi)銷(xiāo),真正壓縮的內(nèi)容(也就是我們前面提到的Deflate數(shù)據(jù),劃線(xiàn)部分都是ZIP文件開(kāi)銷(xiāo))其實(shí)肯定要比原文短(否則ZIP不會(huì)啟用壓縮),我們這個(gè)例子是個(gè)短文本,但對(duì)于更長(zhǎng)的文本而言,那ZIP文件總體長(zhǎng)度肯定是要短于原始文本的。上面的這個(gè)ZIP文件,可以看到好幾個(gè)以PK開(kāi)頭的區(qū)域,也就是不同顏色的劃線(xiàn)區(qū)域,這些其實(shí)都是ZIP文件本身的開(kāi)銷(xiāo)。

所以,我們首先來(lái)看一看ZIP的格式,其格式定義為:

[local file header 1]
[file data 1]
[data descriptor 1]
..........
[local file header n]
[file data n]
[data descriptor n]
[archive decryption header]?
[archive extra data record]?
[central directory]
[zip64 end of central directory record]
[zip64 end of central directory locator]?
[end of central directory record]
local file header+file data+data descriptor這是一段ZIP壓縮數(shù)據(jù),在一個(gè)ZIP文件里,至少有一段,至多那就不好說(shuō)了,假如你要壓縮的文件一共有10個(gè),那這個(gè)地方至少會(huì)有10段,ZIP對(duì)每個(gè)文件進(jìn)行了獨(dú)立壓縮,RAR在此進(jìn)行了改進(jìn),將多個(gè)文件聯(lián)合起來(lái)進(jìn)行壓縮,提高了壓縮率。local file header的格式如下:

可見(jiàn),起始的4個(gè)字節(jié)就是0x50(P)、0x4B(K)、0x03、0x04,因?yàn)槭堑妥止?jié)優(yōu)先,所以Signature=0x03044B50.接下來(lái)的內(nèi)容按照上面的格式解析,十分簡(jiǎn)單,這個(gè)區(qū)域在上面ZIP數(shù)據(jù)的那個(gè)圖里面是紅色劃線(xiàn)區(qū)域,之后則是壓縮后的Deflate數(shù)據(jù)。在文件的尾部,還有ZIP尾部數(shù)據(jù),上面這個(gè)例子包含了central directory和end of central directory record,一般這兩部分也是必須的。central directory以0x50、0x4B、0x01、0x02開(kāi)頭;end of central directory record以0x50、0x4B、0x05、0x06開(kāi)頭,其含義比較簡(jiǎn)單,分別對(duì)應(yīng)于上面ZIP數(shù)據(jù)那個(gè)圖的藍(lán)色和綠色部分,下面是兩者的格式:

end of central directory record格式:

這幾張圖是我從網(wǎng)上找的,寫(xiě)得比較清晰。對(duì)于其中的含義,解釋起來(lái)也比較簡(jiǎn)單,我分析的結(jié)果如下:注意ZIP采用的低字節(jié)優(yōu)先,在一個(gè)字節(jié)里面低位優(yōu)先,需要反過(guò)來(lái)看。

Local File Header: (38B,304b)
00001010110100101100000000100000 (signature)
0000000000010100 (version:20)
0000000000000000 (generalBitFlag)
0000000000001000 (compressionMethod:8)
0100110110001110 (lastModTime:19854)
0100010100100101 (lastModDate:17701)
01010100101011010100001100111100 (CRC32)
00000000000000000000000001001000 (compressedSize:72)
00000000000000000000000001010000 (uncompressedSize:80)
0000000000001000 (filenameLength:8)
0000000000000000 (extraFieldLength:0)
0010101010100110110011100010111001110100001011100001111000101110 (fileName:Test.txt)
?(extraField)


Central File Header: (54B,432b)
00001010110100101000000001000000 (signature)
0000000000010100 (versionMadeBy:20)
0000000000010100 (versionNeeded:20)
0000000000000000 (generalBitFlag)
0000000000001000 (compressionMethod:8)
0100110110001110 (lastModTime:19854)
0100010100100101 (lastModDate:17701)
01010100101011010100001100111100 (CRC32)
00000000000000000000000001001000 (compressedSize:72)
00000000000000000000000001010000 (uncompressedSize:80)
0000000000001000 (filenameLength:8)
0000000000000000 (extraFieldLength:0)
0000000000000000 (fileCommenLength:0)
0000000000000000 (diskNumberStart)
0000000000000001 (internalFileAttr)
10000001100000000000000000100000 (externalFileAttr)
00000000000000000000000000000000 (relativeOffsetLocalHeader)
0010101010100110110011100010111001110100001011100001111000101110 (fileName:Test.txt)
?(extraField)
?(fileComment)


end of Central Directory Record: (22B,176b)
00001010110100101010000001100000 (signature)
0000000000000000 (numberOfThisDisk:0)
0000000000000000 (numberDiskCentralDirectory:0)
0000000000000001 (EntriesCentralDirectDisk:1)
0000000000000001 (EntriesCentralDirect:1)
00000000000000000000000000110110 (sizeCentralDirectory:54)
00000000000000000000000001101110 (offsetStartCentralDirectory:110)
0000000000000000 (fileCommentLength:0)
?(fileComment)

Local File Header Length:304
Central File Header Length:432
End Central Directory Record Length:176

可見(jiàn),開(kāi)銷(xiāo)總的長(zhǎng)度為38+54+22=114字節(jié),整個(gè)文件長(zhǎng)度為186字節(jié),因此Deflate壓縮數(shù)據(jù)長(zhǎng)度為72字節(jié)(576比特)。盡管這里看起來(lái)只是從80字節(jié)壓縮到72字節(jié),那是因?yàn)檫@是一段短文本,重復(fù)字符串出現(xiàn)較少,但如果文本較長(zhǎng),那壓縮率就會(huì)增加,這里只是舉個(gè)例子。

下面對(duì)其中的關(guān)鍵部分,也就是Deflate壓縮數(shù)據(jù)進(jìn)行解析。

?

10,Deflate解碼過(guò)程實(shí)例分析

我們按照Z(yǔ)IP格式把Deflate壓縮數(shù)據(jù)(72字節(jié))提取出來(lái),如下(每行8字節(jié)):

1010100001010011100010111011000000000001000001000011000010100010
1000101110101010011110110000000001100011101110000011100010100101
0101001111001100000010001101001010010010000101101010101100001101
1011110100011111100011101111111001110010011101110110011100010101
0010110100010100101100110001100100000100110111101101111000011101
0010001001100110111001000010011001101010101000110110000001110101
0100011010010011100010110111001000111101101001011100101010010111
0111000011111000011110000011010111001011011111111100100010001001
1010001100001110000010101010111101101010100101111101011111100000

Deflate格式除了上面的介紹,也可以參考RFC1951,解析如下:

Header:101, 第一個(gè)比特是1,表示此部分為最后一個(gè)壓縮數(shù)據(jù)塊;后面的兩個(gè)比特01表示采用動(dòng)態(tài)哈夫曼、靜態(tài)哈夫曼、或者沒(méi)有編碼的標(biāo)志,01表示采用動(dòng)態(tài)Huffman;在RFC1951里面是這么說(shuō)明的:

00 - no compression

01 - compressed with fixed Huffman codes

10 - compressed with dynamic Huffman codes

11 - reserved (error)

注意,這里需要按照低比特在先的方式去看,否則會(huì)誤以為是靜態(tài)Huffman。

接下來(lái):
HLIT:01000,記錄literal/length碼樹(shù)中碼長(zhǎng)序列個(gè)數(shù)的一個(gè)變量,表示HLIT=2(低位在前),說(shuō)明后面存在HLIT + 257=259個(gè)CL1,CL1即0-258被編碼后的長(zhǎng)度,其中0-255表示Literal,256表示無(wú)效符號(hào),257、258分別表示Length=3、4(length從3開(kāi)始)。因此,這里實(shí)際上只出現(xiàn)了兩種重復(fù)字符串的長(zhǎng)度,即3和4。回顧這個(gè)圖可以更清楚:

繼續(xù):
HDIST:01010,記錄distance碼樹(shù)中碼長(zhǎng)序列個(gè)數(shù)的一個(gè)變量,表示HDIST=10,說(shuō)明后面存在HDIST+1=11個(gè)CL2,CL2即Distance Code=0-10被編碼的長(zhǎng)度。

繼續(xù):

HCLEN:0111,記錄Huffman碼樹(shù)3中碼長(zhǎng)序列個(gè)數(shù)的一個(gè)變量,表示HCLEN=14(1110二進(jìn)制),即說(shuō)明緊接著跟著HCLEN+4=18個(gè)CCL,前面已經(jīng)分析過(guò),CCL記錄了一個(gè)Huffman碼表,這個(gè)碼表可以用一個(gè)碼長(zhǎng)序列表示,根據(jù)這個(gè)碼長(zhǎng)序列可以得到碼表。于是接下來(lái)我們把后面的18*3=54個(gè)比特拷貝出來(lái),上面的碼流目前解析為下面的結(jié)果:

101(Header) 01000(HLIT) 01010(HDIST) 0111(HCLEN)?
000 101 110 110 000 000 000 010 000 010 000 110 000 101 000 101 000 101 (CCL)
110101010011110110000000001100011101110000011100010100101
0101001111001100000010001101001010010010000101101010101100001101
1011110100011111100011101111111001110010011101110110011100010101
0010110100010100101100110001100100000100110111101101111000011101
0010001001100110111001000010011001101010101000110110000001110101
0100011010010011100010110111001000111101101001011100101010010111
0111000011111000011110000011010111001011011111111100100010001001
1010001100001110000010101010111101101010100101111101011111100000

標(biāo)準(zhǔn)的CCL長(zhǎng)度為19(回憶一下:CCL范圍為0-18,按照整數(shù)大小排序記錄各自的碼字長(zhǎng)度),因此最后一個(gè)補(bǔ)0。得到序列:

000 101 110 110 000 000 000 010 000 010 000 110 000 101 000 101 000 101 000

其長(zhǎng)度分別為(低位在前):
0、5、3、3、0、0、0、2、0、2、0、3、0、5、0、5、0、5、0
前面已經(jīng)分析過(guò),這個(gè)CCL序列實(shí)際上是經(jīng)過(guò)一次置換操作得到的,需要進(jìn)行相反的置換,置換后為:

3、5、5、5、3、2、2、0、0、0、0、0、0、0、0、0、0、5、3
這個(gè)就是對(duì)應(yīng)于0-18的碼字長(zhǎng)度序列。
根據(jù)Deflate樹(shù)的構(gòu)造方式,得到下面的碼表(Huffman碼表3):

00????? <-->?? 5
01????? <-->?? 6
100???? <-->? 0
101???? <-->? 4
110???? <-->? 18
11100?? <-->1
11101?? <-->2
11110?? <-->3
11111?? <-->17

接下來(lái)就是CL1序列,按照前面的指示,一共有259個(gè),分別對(duì)應(yīng)于literal/length:0-258對(duì)應(yīng)的碼字長(zhǎng)度序列,我們隊(duì)跟著CCL后面的比特按照上面獲得的碼表進(jìn)行逐步解碼,在解碼之前,實(shí)際上并不知道CL1的比特流長(zhǎng)度有多少,需要根據(jù)259這個(gè)數(shù)字來(lái)判定,解完了259個(gè)整數(shù),表明解析CL1完畢:

101(Header) 01000(HLIT) 01010(HDIST) 0111(HCLEN)?
000 101 110 110 000 000 000 010 000 010 000 110 000 101 000 101 000 101 (CCL)

110(18)1010100(7比特,記錄連續(xù)的11-138個(gè)0,此處一共0010101b=21,即記錄21+11=32個(gè)0)

11110(3)110(18)0000000(7比特,記錄連續(xù)的11-138個(gè)0,此處為全0,即記錄0+11=11個(gè)0)

01(6)100(0)01(6)110(18)1110000(7比特,記錄連續(xù)的11-138個(gè)0,此處為111b=7,即記錄7+11=18個(gè)0)

01(6)110(18)0010100(7比特,記錄連續(xù)的11-138個(gè)0,此處為10100b=20,即記錄20+11=31個(gè)0)

101(4)01(6)01(6)00(5)11110(3)01(6)100(0)00(5)00(5)100(0)01(6)101(4)

00(5)101(4)00(5)100(0)100(0)00(5)101(4)101(4)01(6)01(6)01(6)100(0)

00(5)110(18)1101111(7比特,記錄連續(xù)的11-138個(gè)0,此處為1111011b=123,即記錄123+11=134個(gè)0)

統(tǒng)計(jì)一下,上面已經(jīng)解了32+11+18+31+134+30=256個(gè)數(shù)了,因?yàn)榭偣?59個(gè),還差三個(gè):

01(6)00(5)01(6)

好了,CL1比特流解析完畢了,得到的CL1碼長(zhǎng)序列為:

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

總共259個(gè),每行40個(gè)。根據(jù)這個(gè)序列,同樣按照Deflate樹(shù)構(gòu)造方法,得到literal/length碼表(Huffman碼表1)為:

000???? --> (System.Char)(看前面的CL1序列,空格對(duì)應(yīng)的ASCII為0x20=32,碼字長(zhǎng)度3,即上面序列中第一個(gè)3)
001???? -->e(System.Char)
0100??? -->a(System.Char)
0101??? -->l(System.Char)
0110??? -->n(System.Char)
0111??? -->s(System.Char)
1000??? -->t(System.Char)
10010?? -->d(System.Char)
10011?? -->h(System.Char)
10100?? -->i(System.Char)
10101?? -->m(System.Char)
10110?? -->o(System.Char)
10111?? -->r(System.Char)
11000?? -->y(System.Char)
11001?? -->3(System.Int32)(看前面的CL1序列,對(duì)應(yīng)257,碼字長(zhǎng)度5)
110100? -->,(System.Char)
110101? -->.(System.Char)
110110? -->A(System.Char)
110111? -->b(System.Char)
111000? -->c(System.Char)
111001? -->f(System.Char)
111010? -->k(System.Char)
111011? -->u(System.Char)
111100? -->v(System.Char)
111101? -->w(System.Char)
111110? -->-1(System.Int32)(看前面的CL1序列,對(duì)應(yīng)256,碼字長(zhǎng)度6)
111111? -->4(System.Int32)(看前面的CL1序列,對(duì)應(yīng)258,碼字長(zhǎng)度6)

可以看出,碼表里存在兩個(gè)重復(fù)字符串長(zhǎng)度3和4,當(dāng)解碼結(jié)果為-1(上面進(jìn)行了處理,即256),或者說(shuō)遇到111110的時(shí)候,表示Deflate碼流結(jié)束。

按照同樣的道理,對(duì)CL2序列進(jìn)行解析,前面已經(jīng)知道HDIST=10,即有11個(gè)CL2整數(shù)序列:

11111(17)000(3比特,記錄連續(xù)的3-10個(gè)0,此處為0,即記錄3個(gè)0)

11101(2)11111(17)100(3比特,記錄連續(xù)的3-10個(gè)0,此處為001b=1,即記錄4個(gè)0)

11100(1)100(0)11101(2)

已經(jīng)結(jié)束,總共11個(gè)。

于是CL2序列為:

0 0 0 2 0 0 0 0 1 0 2

分別記錄的是distance碼為0-10的碼字長(zhǎng)度,根據(jù)下面的對(duì)應(yīng)關(guān)系,需要進(jìn)行擴(kuò)展:

比如,第1個(gè)碼長(zhǎng)2記錄的是Code=3的長(zhǎng)度,即Distance=4對(duì)應(yīng)的碼字為:

10????? -->4(System.Int32)

第1個(gè)碼長(zhǎng)1記錄的是Code=8的長(zhǎng)度(碼字為0,擴(kuò)展三位000-111),即Distance=17-24對(duì)應(yīng)的碼字為(注意,低比特優(yōu)先):

0 000??? -->17(System.Int32)
0 100??? -->18(System.Int32)
0 010??? -->19(System.Int32)
0 110??? -->20(System.Int32)
0 001??? -->21(System.Int32)
0 101??? -->22(System.Int32)
0 011??? -->23(System.Int32)
0 111??? -->24(System.Int32)

注意,擴(kuò)展的時(shí)候還是低比特優(yōu)先。

最后1個(gè)碼長(zhǎng)2記錄的是Code=10的長(zhǎng)度(其實(shí)是碼字:11,擴(kuò)展四位0000-1111),即Distance=33-48對(duì)應(yīng)的碼字為:

11 0000? -->33(System.Int32)
11 1000? -->34(System.Int32)
11 0100? -->35(System.Int32)
11 1100? -->36(System.Int32)
11 0010? -->37(System.Int32)
11 1010? -->38(System.Int32)
11 0110? -->39(System.Int32)
11 1110? -->40(System.Int32)
11 0001? -->41(System.Int32)
11 1001? -->42(System.Int32)
11 0101? -->43(System.Int32)
11 1101? -->44(System.Int32)
11 0011? -->45(System.Int32)
11 1011? -->46(System.Int32)
11 0111? -->47(System.Int32)
11 1111? -->48(System.Int32)

至此為止,Huffman碼表1、Huffman碼表2已經(jīng)還原出來(lái),接下來(lái)是對(duì)LZ壓縮所得到的literal、distance、length進(jìn)行解碼,目前剩余的比特流如下,先按照Huffman碼表1解碼,如果解碼結(jié)果是長(zhǎng)度(>256),則接下來(lái)按照Huffman碼表2解碼,逐步解碼即可:

[As ]:110110(A)0111(s)000(空格)

[mentioned ]:10101(m)001(e)0110(n)1000(t)10100(i)10110(o)0110(n)001(e)10010(d)000(空格)

[above,]:0100(a)110111(b)10110(o)111100(v)001(e)110100(,)

[there ]:1000(t)10011(h)001(e)10111(r)001(e)000(空格)

[are ]:0100(a)11001(長(zhǎng)度3,表示下一個(gè)需要用Huffman解碼)10(Distance=4,即重復(fù)字符串為re空格)

[many ]:10101(m)0100(a)0110(n)11000(y)000(空格)

[kinds ]:111010(k)10100(i)0110(n)10010(d)0111(s)000(空格)

[of ]:10110(o)111001(f)000(空格)

[wireless ]:111101(w)10100(i)10111(r)001(e)0101(l)001(e)0111(s)0111(s)000(空格)

[systems o]:0111(s)11000(y)0111(s)1000(t)001(e)10101(m)11001(長(zhǎng)度指示=3,接下來(lái)根據(jù)distance解碼)0110(Distance=20,即重復(fù)字符串為s o)

[ther ]:111111(長(zhǎng)度指示=4,接下來(lái)根據(jù)distance解碼)111001(Distance=42,即重復(fù)字符串為ther)000(空格)

[than ]:1000(t)10011(h)0100(a)0110(n)000(空格)

[cellular.]:111000(c)001(e)0101(l)0101(l)111011(u)0101(l)0100(a)10111(r)110101(.)

[256,結(jié)束標(biāo)志]111110(結(jié)束標(biāo)志)0000(字節(jié)補(bǔ)齊的0)

于是解壓縮結(jié)果為:

As mentioned above,there are many kinds of wireless systems other than cellular.

再來(lái)回顧我們的解碼過(guò)程:

譯碼過(guò)程:
1、根據(jù)HCLEN得到截尾信息,并參照固定置換表,根據(jù)CCL比特流得到CCL整數(shù)序列;
2、根據(jù)CCL整數(shù)序列構(gòu)造出等價(jià)于CCL的二級(jí)Huffman碼表3;
3、根據(jù)二級(jí)Huffman碼表3對(duì)CL1、CL2比特流進(jìn)行解碼,得到SQ1整數(shù)序列,SQ2整數(shù)序列;
4、根據(jù)SQ1整數(shù)序列,SQ2整數(shù)序列,利用游程編碼規(guī)則得到等價(jià)的CL1整數(shù)序列、CL2整數(shù)序列;
5、根據(jù)CL1整數(shù)序列、CL2整數(shù)序列分別構(gòu)造兩個(gè)一級(jí)Huffman碼表:literal/length碼表、distance碼表;
6、根據(jù)兩個(gè)一級(jí)Huffman碼表對(duì)后面的LZ壓縮數(shù)據(jù)進(jìn)行解碼得到literal/length/distance流;
7、根據(jù)literal/length/distance流按照LZ規(guī)則進(jìn)行解碼。

Deflate碼流長(zhǎng)度總共為72字節(jié)=576比特,其中:

3比特Header;

5比特HLIT;

5比特HDIST;

4比特HCLEN;

54比特CCL序列碼流;

133比特CL1序列碼流;

34比特CL2序列碼流;

338比特LZ壓縮后的literal/length/distance碼流。

11、ZIP的其它說(shuō)明

上面各個(gè)環(huán)節(jié)已經(jīng)詳細(xì)分析了ZIP壓縮的過(guò)程以及解碼流程,通過(guò)對(duì)一個(gè)實(shí)例的解壓縮過(guò)程分析,可以徹底地掌握Z(yǔ)IP壓縮和解壓縮的原理和過(guò)程。還有一些情況需要說(shuō)明:

(1)上面的算法復(fù)雜度主要在于壓縮一端,因?yàn)樾枰y(tǒng)計(jì)literal/length/distance,創(chuàng)建動(dòng)態(tài)Huffman碼表,相反解壓只需要還原碼表后,逐比特解析即可,這也是壓縮軟件的一個(gè)典型特點(diǎn),解壓速度遠(yuǎn)快于壓縮速度。

(2)上面我們分析了動(dòng)態(tài)Huffman,對(duì)于LZ壓縮后的literal/length/distance,也可以采用靜態(tài)Huffman編碼,這主要取決于ZIP在壓縮中看哪種方式更節(jié)省空間,靜態(tài)Huffman編碼不需要記錄碼表,因?yàn)檫@個(gè)碼表是固定的,在RFC1951里面也有說(shuō)明。對(duì)于literal/length碼表來(lái)說(shuō),需要對(duì)0-285進(jìn)行編碼,其碼表為:

對(duì)于Distance來(lái)說(shuō),需要對(duì)Code=0-29的數(shù)進(jìn)行編碼,則直接采用5比特表示。Distance和動(dòng)態(tài)Huffman一樣,在此基礎(chǔ)上進(jìn)行擴(kuò)展。

(3)ZIP中使用的LZ77算法是一種改進(jìn)的LZ77。主要區(qū)別有兩點(diǎn):

1)標(biāo)準(zhǔn)LZ77在找到重復(fù)字符串時(shí)輸出三元組(length, distance, 下一個(gè)未匹配的字符)(有興趣可以關(guān)注LZ77那篇論文);Deflate在找到重復(fù)字符串時(shí)僅輸出雙元組(length, distance)。
2)標(biāo)準(zhǔn)LZ77使用”貪婪“的方式解析,尋找的都是最長(zhǎng)匹配字符串。Deflate中不完全如此。David Salomon的書(shū)里給了一個(gè)例子:

對(duì)于上面這個(gè)例子,標(biāo)準(zhǔn)LZ77在滑動(dòng)窗口中查找最長(zhǎng)匹配字符串,找到的是"the"與前面的there的前三個(gè)字符匹配,這種貪婪解析方式邏輯簡(jiǎn)單,但編碼效率不一定最高。Deflate則不急于輸出,跳過(guò)t繼續(xù)往后查看,發(fā)現(xiàn)"th ne"這5個(gè)字符存在重復(fù)字符串,因此,Deflate算法會(huì)選擇將t作為未匹配字符輸出,而對(duì)后面的匹配字符串用(length, distance)編碼輸出。顯然,這樣就提高了壓縮效率,因?yàn)闃?biāo)準(zhǔn)的LZ77找到的重復(fù)字符串長(zhǎng)度為3,而Deflate找到的是5。換句話(huà)說(shuō),Deflate算法并不是簡(jiǎn)單的尋找最長(zhǎng)匹配后輸出,而是會(huì)權(quán)衡幾種可行的編碼方式,用其中最高效的方式輸出。

?

12、總結(jié)

本篇博文對(duì)ZIP中使用的壓縮算法進(jìn)行了詳細(xì)分析,從一個(gè)簡(jiǎn)單地例子出發(fā),一步步地分析了PK設(shè)計(jì)Deflate算法的思路。最后,通過(guò)一個(gè)實(shí)際例子,分析了其解壓縮流程。總的來(lái)看,ZIP的核心在于如何對(duì)LZ壓縮后的literal、length、distance進(jìn)行Huffman編碼,以及如何以最小空間記錄Huffman碼表。整個(gè)過(guò)程充滿(mǎn)了對(duì)數(shù)據(jù)結(jié)構(gòu)尤其是樹(shù)的深入優(yōu)化利用。按照上面的分析,如果要對(duì)ZIP進(jìn)行進(jìn)一步改進(jìn),可以考慮的地方也有不少,典型的有:

(1)擴(kuò)大LZ編碼的滑動(dòng)窗口的大小;

(2)將Huffman編碼改進(jìn)為算術(shù)編碼等壓縮率更高的方法,畢竟,Huffman的碼字長(zhǎng)度必須為整數(shù),這就從理論上限制了它的壓縮率只能接近于理論極限,但難以達(dá)到。我記得在JPEG圖像編碼領(lǐng)域,以前的JPEG采用了DCT變換編碼+Huffman的方式,現(xiàn)在JPEG2000將其改為小波變換+算數(shù)編碼,所以數(shù)據(jù)壓縮也可以嘗試類(lèi)似的思路;

(3)將多個(gè)文件進(jìn)行合并壓縮,ZIP中,不同的文件壓縮過(guò)程沒(méi)有關(guān)系,獨(dú)立進(jìn)行,如果將它們合并起來(lái)一起進(jìn)行壓縮,壓縮率可以得到進(jìn)一步提高。

?

描述分析有誤的地方,敬請(qǐng)指正。針對(duì)數(shù)據(jù)壓縮相關(guān)的話(huà)題,后續(xù)會(huì)對(duì)HBase列壓縮等等進(jìn)行分析,看看ZIP這種文件壓縮和HBase這種數(shù)據(jù)庫(kù)數(shù)據(jù)壓縮的區(qū)別和聯(lián)系。

總結(jié)

以上是生活随笔為你收集整理的文件压缩算法详细分析(ZIP)及解压实例解释的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

国产 一区二区三区 在线 | 91av亚洲 | 成人免费亚洲 | 高清av免费看 | 欧美做受高潮1 | 中文字幕色综合网 | 黄色毛片网站在线观看 | 久久的色 | 又爽又黄又无遮挡网站动态图 | 天天天天射 | 日韩欧美视频在线 | 国产一级做a | 日韩精品在线一区 | 高清av在线免费观看 | 久草在线久草在线2 | 久久综合精品国产一区二区三区 | 久久草网站| 黄色小说免费在线观看 | 夜夜操狠狠干 | 亚洲天堂自拍视频 | 视频在线在亚洲 | 人人cao| 精品久久久一区二区 | 狠狠干天天操 | 国产精品毛片久久久久久久 | 成人av动漫在线 | 亚洲精品久久久久中文字幕二区 | 久久久久久久免费看 | 国产成人三级在线播放 | 国产 视频 高清 免费 | 亚洲国产资源 | 狠狠干美女 | 国产美腿白丝袜足在线av | 高潮久久久 | 国产精品av一区二区 | 精品久久久久国产免费第一页 | 五月婷婷,六月丁香 | 国产一区二区在线免费 | 国产一区视频导航 | 日韩欧美99 | 97人人模人人爽人人喊网 | 国产啊v在线| 九九视频免费观看视频精品 | 亚洲91中文字幕无线码三区 | 99中文字幕视频 | 国产视频在线观看免费 | 亚洲涩涩涩涩涩涩 | 精品影院一区二区久久久 | 天天综合精品 | 国产中文字幕在线看 | 97热视频| 麻豆久久久久久久 | 亚洲一级电影在线观看 | 最新一区二区三区 | 六月婷色 | 91亚·色| 国产视频黄 | 日韩videos高潮hd | 亚洲在线高清 | 国产一区视频免费在线观看 | 黄色av电影 | 亚洲精品国精品久久99热 | av最新资源| 国产成人精品综合 | 中文字幕av专区 | 欧美日韩18 | 91色网址 | 夜夜操天天干 | 一级黄色片毛片 | 国产精品va在线播放 | 亚洲日本激情 | 91色九色 | 国产中文字幕三区 | 91毛片视频 | 国产91九色蝌蚪 | 天天曰天天 | 国产美女精品视频 | 中文字幕资源网 国产 | 精品在线观 | 亚洲精品国偷拍自产在线观看蜜桃 | 欧美在线观看小视频 | 在线免费观看亚洲视频 | 国产成人av一区二区三区在线观看 | 成年在线观看 | 国产福利电影网址 | 国产老妇av | a√资源在线 | 美女久久久久久久久久久 | 国产精成人品免费观看 | 麻豆影音先锋 | 久久久久久免费网 | 成人免费观看a | 久久久久久蜜桃一区二区 | 91女人18片女毛片60分钟 | 精品久久久久久久久久久久久 | 91精品国产福利在线观看 | 国产亚洲在 | 99精品视频观看 | 久久久五月天 | 狠狠色伊人亚洲综合网站野外 | 免费看一及片 | 13日本xxxxxⅹxxx20 | 在线观看日本高清mv视频 | 一区二区三区四区精品视频 | 婷婷在线视频观看 | 午夜婷婷在线观看 | 激情综合网五月激情 | 又黄又爽又色无遮挡免费 | 三级黄免费看 | 国产精品日韩久久久久 | 国产精品理论在线观看 | 在线国产中文字幕 | 97电院网手机版 | 国产精品日韩久久久久 | 国产精品久久久久久久免费 | 久久久 精品 | 亚洲男人天堂2018 | 久久精品久久久久久久 | 久草在线播放视频 | 粉嫩高清一区二区三区 | 香蕉国产91| 国产精品videossex国产高清 | 在线观看韩国av | 亚洲国产欧美一区二区三区丁香婷 | 国产成人免费网站 | 成人在线视频在线观看 | 五月天久久久 | 国产视频精选在线 | 91污视频在线观看 | 波多野结衣精品视频 | 久久午夜国产精品 | 欧美日韩中 | 国产综合片 | 97人人添人澡人人爽超碰动图 | 久久精品艹 | 午夜视频免费 | 黄色网www | 日韩二三区| 色99视频 | 99综合电影在线视频 | 五月婷婷综合网 | 天堂av在线中文在线 | 午夜久久久久久久久久久 | 8x成人在线 | 日韩在线视频免费观看 | 99日韩精品| 日韩丝袜视频 | 欧美精品xx | 成人四虎| 中文字幕日韩无 | 国产黄色精品视频 | 成 人 黄 色视频免费播放 | 婷婷精品在线视频 | 国产亚洲婷婷免费 | 免费手机黄色网址 | www.超碰97.com| 婷婷色网 | 狠狠狠的干| 人人讲下载 | 日韩亚洲在线观看 | 超碰在线免费福利 | 天天干天天想 | 五月激情电影 | 国产精品大全 | 亚洲午夜久久久久久久久 | 久久精品国产美女 | 天天综合亚洲 | 一区二区中文字幕在线观看 | 日本久久精品 | 免费精品人在线二线三线 | 这里有精品在线视频 | 奇米影视777四色米奇影院 | 日韩欧美一区二区三区在线观看 | 亚洲丝袜一区二区 | 国产一区久久 | 特级西西www44高清大胆图片 | 91一区二区三区在线观看 | www.天天草 | 国产超碰在线观看 | 亚洲综合在线发布 | 午夜视频二区 | 丁香花中文在线免费观看 | 91亚洲精品久久久蜜桃 | 一级精品视频在线观看宜春院 | 四虎免费在线观看 | 欧美一区二区精美视频 | 欧美精品成人在线 | 国产精品久久久视频 | 91手机视频在线 | 久久久久久久久久久电影 | 爱情影院aqdy鲁丝片二区 | 亚洲日b视频 | 国产91av视频在线观看 | 国产一区二区三区视频在线 | 三日本三级少妇三级99 | 久久精品网址 | 免费麻豆 | 天天看天天操 | 久久黄色成人 | 色婷婷亚洲综合 | 天天爽天天搞 | 日韩成人精品一区二区三区 | 色婷婷久久久 | 欧美视频99 | 欧美一级片免费观看 | 人人爽人人爽人人片 | 亚洲精品在线二区 | 久久成年人视频 | 亚洲人在线视频 | 色黄久久久久久 | 一区三区视频在线观看 | 麻豆视频国产在线观看 | 亚洲精品自在在线观看 | 日韩精品一区不卡 | 四虎在线观看精品视频 | 在线国产视频 | 青青草久草在线 | 午夜999| 国内视频在线 | 色永久免费视频 | 男女激情麻豆 | 日韩中文字幕在线不卡 | 亚洲久在线 | 韩日电影在线观看 | 超碰在线天天 | 狠狠狠的干 | 99精品在线观看视频 | 在线成人一区 | 久久免费观看少妇a级毛片 久久久久成人免费 | 综合色中文 | 手机在线欧美 | 天天干天天干天天色 | 日本成人黄色片 | 日韩免费电影在线观看 | 久草在线官网 | 亚洲精品动漫成人3d无尽在线 | 欧美aa级| 久草在线视频精品 | 国产精品视频永久免费播放 | 欧美日韩在线视频一区 | 91精品国产综合久久婷婷香蕉 | 成人国产综合 | 人人藻人人澡人人爽 | 日本高清xxxx| 中文字幕一区在线观看视频 | 丁香综合 | 97超碰中文字幕 | 国产精品wwwwww | 99夜色| 91看片淫黄大片一级在线观看 | 精品视频久久久久久 | 久久精品网址 | 亚洲专区 国产精品 | 亚洲国产大片 | 国产一级免费播放 | 成人亚洲综合 | 96视频免费在线观看 | 久久99精品国产99久久6尤 | 91精品国自产在线观看 | 国产精品原创av片国产免费 | 激情小说 五月 | 国产精品一区二区av麻豆 | 欧美黑人性爽 | 天天插天天 | 亚洲国产美女久久久久 | 91中文在线观看 | 国产在线无 | 国产精品av久久久久久无 | 久久一级电影 | 久草免费在线 | 国产一级片一区二区三区 | 国产一区免费看 | 欧美精品中文 | 狠狠色狠狠色合久久伊人 | 午夜在线免费视频 | 精品国产a | 精品a级片 | 中文字幕 在线 一 二 | 久久久黄视频 | 中文字幕人成乱码在线观看 | 亚洲视频免费在线看 | 国产黄色成人av | 国产亚洲无| 在线观看视频国产 | 日韩区视频| 免费在线观看视频a | 在线韩国电影免费观影完整版 | 黄色特一级 | 午夜精品一区二区三区视频免费看 | 国产在线播放一区 | 久久精品直播 | 黄色精品一区二区 | 国产精品日韩久久久久 | 91精品久久久久久 | 九七在线视频 | 91污污视频在线观看 | 欧洲精品久久久久毛片完整版 | 日韩一级成人av | 国产日韩欧美在线观看 | 99爱这里只有精品 | 国产亚洲精品久久久久久久久久 | 人人艹视频 | 久久精品视频国产 | 色视频在线| 欧美一级在线观看视频 | 久草在线视频在线 | 欧美性视频网站 | 免费观看黄色12片一级视频 | 免费中文字幕视频 | 国产69久久 | 91毛片在线| 免费a视频 | 中文字幕在线字幕中文 | 久久99国产精品二区护士 | www色com | 欧美日韩国产在线 | 一级片视频在线 | 国产美女视频免费观看的网站 | 亚洲国产成人高清精品 | 国产亚洲一区二区在线观看 | 国产a网站 | 国产中文字幕免费 | 国产精在线 | 久久精品一区二区三区视频 | 久久成人久久 | 亚洲一区二区三区四区在线视频 | 久久精品伊人 | 久久久久久久久电影 | 国产精品九九热 | 一级免费黄色 | 日韩va亚洲va欧美va久久 | 精品国产aⅴ麻豆 | 久操操| 综合色在线观看 | 国产九色在线播放九色 | 97视频播放 | 日本精品一区二区三区在线观看 | 日韩av中文在线 | 日日夜av| 国产精品美女久久久久久免费 | adn—256中文在线观看 | 久久综合久久综合九色 | 天天躁日日 | 国产视频精品在线 | 国产精品com | 超碰久热 | 国产婷婷vvvv激情久 | 久久99精品久久久久蜜臀 | 国产视频亚洲精品 | 五月黄色| 丁香资源影视免费观看 | 一级黄色在线视频 | a黄色片 | 免费又黄又爽 | 色综合久久综合网 | 国产一级黄 | 性色av一区二区三区在线观看 | 久久免费视频2 | 69国产盗摄一区二区三区五区 | 国产拍揄自揄精品视频麻豆 | 免费看黄视频 | 玖玖爱免费视频 | 成人毛片在线观看 | 91最新国产| 日韩欧美在线观看 | 国产色中涩 | 色姑娘综合 | 在线亚洲播放 | 天天天插| 成人国产精品久久久久久亚洲 | 成人黄色免费观看 | 国产精品精品国产色婷婷 | 精品国内自产拍在线观看视频 | 99精品国产一区二区三区麻豆 | 日韩videos高潮hd | 久久观看| 久久精品理论 | 午夜精品电影 | 久久成人高清视频 | 久久久久免费精品 | 亚洲国产精彩中文乱码av | 久久婷亚洲五月一区天天躁 | 最近日本中文字幕a | 色天天综合久久久久综合片 | 人人草在线观看 | 国产美女精彩久久 | 色wwww| 国产色久 | 久久久在线免费观看 | 国产99久久久欧美黑人 | 亚洲一区视频在线播放 | 国产精品久久久免费看 | 人人爽人人爽 | 中文字幕资源网 | 国产一区在线免费 | 亚洲国产中文字幕在线视频综合 | 一级黄色在线免费观看 | 夜夜夜草 | 免费亚洲黄色 | 日韩精品网址 | 久久国产香蕉视频 | 欧美日韩一级久久久久久免费看 | 中文字幕在线观看视频一区 | 久久短视频 | 亚洲蜜桃在线 | 999国内精品永久免费视频 | 九九视频这里只有精品 | 最近中文字幕mv免费高清在线 | 亚洲美女在线一区 | 久久久久久国产一区二区三区 | www.国产在线视频 | 天堂成人在线 | 久久再线视频 | 亚洲精品久| 黄网站色视频免费观看 | 福利久久 | av资源在线看 | 五月婷婷丁香综合 | 精品久久久久久亚洲 | 日韩精品一区二区在线视频 | 日韩av视屏| 精品国产乱码久久久久久1区2匹 | 日本三级吹潮在线 | 国产va在线观看免费 | 99在线观看免费视频精品观看 | 综合久久婷婷 | 久久成人欧美 | 成人综合日日夜夜 | 色综合久久久久 | 久久久婷| 日日夜夜精品视频天天综合网 | 成人av网站在线 | 亚洲精品一区二区久 | 国产日韩欧美中文 | 天天操人人干 | 日日成人网 | 久久久国产成人 | 日韩在线理论 | 欧美一级片免费播放 | 亚洲 欧美 日韩 综合 | 五月香视频在线观看 | 精品国产一区在线观看 | 国产精品12| 毛片视频网址 | 久久一区精品 | av先锋中文字幕 | 九九影视理伦片 | 国产剧情在线一区 | 欧美日韩精品在线 | 国产精品刺激对白麻豆99 | 免费看三级 | 97视频免费在线 | 激情av在线资源 | 亚洲电影成人 | 免费在线观看av网站 | 日韩午夜剧场 | 久久超碰网 | 久久66热这里只有精品 | 精品在线视频播放 | a在线观看国产 | 狠狠狠色丁香婷婷综合激情 | 成人在线播放免费观看 | 日本h在线播放 | www.国产在线 | 97在线观看免费 | 日韩激情影院 | 99精品视频在线观看视频 | 中文字幕在线免费观看视频 | 中文在线www | 日韩精品视频在线观看网址 | av永久网址 | 久久精彩 | 日韩中文字幕免费看 | 成人免费 在线播放 | 国产一区精品在线 | 91精品综合在线观看 | 人人爽人人乐 | 色偷偷97 | 九九精品久久久 | 亚洲最大在线视频 | 一区中文字幕 | 亚洲精品在线免费看 | 国产精品一区二区在线观看免费 | 欧美高清视频不卡网 | 日韩夜夜爽 | 国产啊v在线 | 亚洲影视资源 | 1000部18岁以下禁看视频 | 国产黄色一级大片 | 久久精品91视频 | 欧美日韩精品在线免费观看 | 懂色av一区二区三区蜜臀 | 99久久精品免费一区 | 久久久私人影院 | 狠狠色丁香婷婷综合基地 | 五月婷婷丁香网 | 亚洲一区二区精品 | 九九久久电影 | 九九视频精品在线 | 亚洲乱码中文字幕综合 | 亚洲一级片在线观看 | 国产精品国产亚洲精品看不卡 | 国产精品去看片 | 日韩在线视频不卡 | 2019中文最近的2019中文在线 | 欧美成人h版 | 日韩高清无线码2023 | 五月婷婷丁香六月 | 在线精品观看 | 国产成人性色生活片 | 精品视频123区在线观看 | 九色91在线| 黄色在线免费观看网址 | 婷婷久久一区 | 有码一区二区三区 | 五月婷婷在线观看 | 日韩欧美精品一区 | 欧美怡红院视频 | 日日夜夜网 | 国产精品美女网站 | 日韩欧美xxxx | 亚洲成人频道 | 久久精品欧美一区二区三区麻豆 | 热久久精品在线 | 四虎国产精品成人免费4hu | 黄免费网站 | 免费十分钟 | 国产精品国内免费一区二区三区 | 韩国av在线| 国产香蕉视频在线观看 | 超碰个人在线 | 成年人免费看片 | www.狠狠色 | 亚洲国产精品推荐 | 久久久久日本精品一区二区三区 | 精品人妖videos欧美人妖 | 91大片网站 | 久久情侣偷拍 | 最近最新mv字幕免费观看 | 激情五月播播久久久精品 | 成人一级免费电影 | 国产不卡免费 | 麻豆传媒一区二区 | 久久久久国产一区二区三区 | 国产高清视频在线观看 | 久久成人亚洲欧美电影 | 亚洲精品小区久久久久久 | 九九爱免费视频在线观看 | 性色av免费观看 | 一区二区免费不卡在线 | 狠狠色狠狠综合久久 | 欧美视频日韩 | 在线看的毛片 | 91福利视频免费观看 | 国产精品一区二区三区电影 | 国产精品11 | 69人人| 91在线www | 免费看黄在线看 | 日韩高清dvd | 国内久久久久 | 99热99| 精品在线观看视频 | 最近中文字幕免费 | 色婷婷电影网 | 天堂在线视频中文网 | 91九色视频在线 | 久久综合九色综合97_ 久久久 | 91看片淫黄大片在线播放 | 久久福利在线 | 日韩高清无线码2023 | 欧美日韩69 | 久久久国产毛片 | 国产精品毛片一区二区在线看 | 欧美性生活免费看 | 久久人人精品 | 久久久久国产成人免费精品免费 | 久久高清视频免费 | 中文字幕免费高清av | 久久人人爽人人人人片 | 亚洲免费国产视频 | 久久久69 | 国产成人黄色在线 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 蜜臀aⅴ国产精品久久久国产 | 午夜av色 | 欧美日韩另类在线 | 99久热在线精品视频 | 国产精品久久久久一区二区 | 亚洲精品18日本一区app | 色综合色综合久久综合频道88 | 久久精品4 | 天天艹天天干天天 | 在线观看你懂的网址 | 综合网天天| 色的网站在线观看 | 免费能看的av | a√天堂中文在线 | 在线观看视频你懂 | 欧美日性视频 | 日本韩国精品在线 | 蜜臀av性久久久久av蜜臀妖精 | 日韩精品中文字幕在线观看 | 西西www4444大胆在线 | 99夜色 | 日本在线视频一区二区三区 | 免费在线国产精品 | 丁香婷婷网 | 精品久久在线 | 97在线免费视频观看 | 久久精品3| 亚洲高清久久久 | 97视频免费看 | 九九综合在线 | 香蕉久久国产 | 国内丰满少妇猛烈精品播 | 中文字幕有码在线观看 | av免费在线看网站 | 久久深夜 | 日韩网站一区 | 就色干综合 | 久久久精品在线观看 | 日韩免费在线看 | 三级黄色网址 | 色99之美女主播在线视频 | 久久系列 | 国产亚洲成av人片在线观看桃 | 免费视频三区 | 中文字幕在线观看一区二区 | 99看视频在线观看 | 国产精品九九久久99视频 | 久艹视频在线观看 | 久久久国产在线视频 | 中文字幕日本在线观看 | 免费男女网站 | 国产无吗一区二区三区在线欢 | 亚洲蜜桃在线 | 久久视频精品在线 | 国产精品毛片久久久久久久 | 成人免费观看完整版电影 | 高清av免费一区中文字幕 | 米奇狠狠狠888 | 91精品国产91热久久久做人人 | 亚洲精品视频在线观看网站 | 成人黄色免费观看 | 免费在线观看一区 | 在线观看免费视频你懂的 | 2019天天干天天色 | 亚洲国产三级在线 | 婷婷久久一区二区三区 | 夜夜视频 | 国产精品夜夜夜一区二区三区尤 | 一区二区三区免费在线 | 四虎最新入口 | 国产一级黄色电影 | 国产xxxx | 日韩在线短视频 | 国产精品2018 | 日本精品视频在线 | 91av资源在线 | 亚洲精品国产高清 | 国产一级一级国产 | 日韩视频一区二区在线观看 | 中文网丁香综合网 | 中文字幕在线国产 | 激情网站五月天 | 精品国产美女在线 | 久99久在线视频 | 黄色一级大片在线免费看国产一 | 日韩精品视频在线免费观看 | 99久久国产免费免费 | 久艹在线观看视频 | 久久精视频 | 国产精品原创av片国产免费 | 亚洲日本韩国一区二区 | 日本一区二区不卡高清 | 激情五月综合 | 午夜免费电影院 | 超碰在线97国产 | 在线直播av | 成人黄色电影免费观看 | 国产精品美女久久久久久久久久久 | 人人爱人人射 | 麻豆视传媒官网免费观看 | 亚洲黄色av网址 | 99色视频在线 | 伊人www22综合色 | 五月婷婷丁香 | 伊人五月综合 | 精品播放| 国产高清99 | 久久国产露脸精品国产 | av福利在线看 | bbbb操bbbb | 免费观看91视频 | 91色国产在线 | 免费在线播放黄色 | 中文字幕一区二区三区四区在线视频 | japanesefreesexvideo高潮 | 亚洲一区二区麻豆 | 国内精品久久久久影院男同志 | 欧美精品久久久久久久久免 | 国产精品九九久久99视频 | 91九色视频观看 | 波多野结衣资源 | 99精品乱码国产在线观看 | 99久久这里有精品 | 99热精品国产一区二区在线观看 | 亚洲一区不卡视频 | 国产又黄又爽又猛视频日本 | 在线观看av网站 | 久草在线费播放视频 | 久久国产经典视频 | 精品国产一区二区三区久久久久久 | 91九色国产在线 | 亚洲精品国偷自产在线99热 | 免费黄a大片 | 亚洲资源在线 | 一区二区三区日韩视频在线观看 | 日本久久成人中文字幕电影 | 一区二区三区高清在线观看 | 精品在线不卡 | 中文字幕高清有码 | 天天操婷婷 | 日韩欧美精品一区二区 | 天天曰天天爽 | 国产欧美日韩一区 | 91最新网址| 欧美a级在线播放 | 欧美日韩高清在线 | www.婷婷色| 免费看成年人 | 欧美做受高潮 | 在线观看国产高清视频 | 天天色天天操天天爽 | 色婷婷国产 | 成人av一区二区兰花在线播放 | 2019中文最近的2019中文在线 | 九九激情视频 | 不卡av在线 | 欧美一区二区在线免费观看 | 波多野结衣资源 | 日韩免费视频 | 激情婷婷久久 | 91视频这里只有精品 | 国产一区二区中文字幕 | 国产精品自产拍在线观看蜜 | 97色在线观看免费视频 | 国内综合精品午夜久久资源 | 亚洲一级片在线看 | 成人手机在线视频 | 成人久久18免费网站 | 久草香蕉在线 | 精品亚洲成a人在线观看 | 一级性视频 | 国产69久久精品成人看 | 国产成人精品一区二区三区在线 | 欧美成人精品三级在线观看播放 | 一区二区三区日韩在线 | 日韩在线观看免费 | 精品国产欧美一区二区三区不卡 | 精品亚洲欧美一区 | 久久尤物电影视频在线观看 | 日韩在线播放欧美字幕 | 色五月成人 | 五月婷婷在线播放 | 国产精品自拍av | 久草视频播放 | 91成人精品一区在线播放69 | 自拍超碰在线 | 国产视频在线观看一区 | 99久久夜色精品国产亚洲96 | 久久激情视频网 | 91精品免费| 国产精品99久久免费观看 | 欧美做受高潮电影o | 国产亚洲精品久久久久动 | 天天草视频 | 亚洲一区二区精品视频 | 久草在线这里只有精品 | 精品国产欧美一区二区 | 亚洲一区二区天堂 | 日本不卡一区二区 | 天天操夜夜爱 | 亚洲一区二区黄色 | 嫩小bbbb摸bbb摸bbb | 亚洲精品国产精品国自产观看 | 日韩av在线一区二区 | 青青草视频精品 | 免费看片色 | 亚洲精品在线视频播放 | 在线播放一区二区三区 | 麻豆国产精品视频 | 人人爽人人澡人人添人人人人 | 色com网 | 久久桃花网| 天堂av官网| 日韩乱码中文字幕 | 亚洲草视频 | av+在线播放在线播放 | 国内精品久久久久久久久久 | 看黄色91| 亚洲激精日韩激精欧美精品 | 久草在线在线精品观看 | 99色网站 | 日韩精品一区二区久久 | 久久99精品视频 | 人人干,人人爽 | 国产精品美女在线 | 成人a免费| 最新超碰在线 | 久久人91精品久久久久久不卡 | 91.麻豆视频 | 九九在线国产视频 | 福利网在线 | 国产亚洲欧洲 | 香蕉免费在线 | 综合网色 | 99re久久精品国产 | 99在线免费观看 | 黄色三级免费网址 | 国产精品不卡 | 欧美 日韩 性 | 91香蕉国产在线观看软件 | 亚洲午夜精品一区 | 欧美一二三专区 | 国产精品一区二区 91 | 手机在线观看国产精品 | 婷婷五月在线视频 | 婷婷日 | 国产成人免费观看久久久 | 国产精品一区二区62 | 97精品国产97久久久久久粉红 | 久久久久久久久久久影院 | 一区二区三区电影大全 | 国产成人精品亚洲精品 | 人人爽人人舔 | 天天综合精品 | 九色视频网站 | 亚洲综合色婷婷 | 成年人免费电影在线观看 | 天堂资源在线观看视频 | 久久伊人八月婷婷综合激情 | 精品久久在线 | 日韩激情视频 | 欧美伦理一区二区三区 | 欧洲精品一区二区 | 久久精品视频在线观看 | 久久看片 | 免费视频一二三 | 草久热 | 91视频麻豆 | 欧美日韩精品网站 | 99精品视频免费看 | 精品久久久久久久久久久久 | 国产精品乱码高清在线看 | 超碰在线免费97 | 91最新地址永久入口 | 国产精品av久久久久久无 | 波多野结衣资源 | 97在线免费观看 | 高潮毛片无遮挡高清免费 | 夜夜操天天干 | 黄色av网站在线免费观看 | 亚洲精品视频在线观看免费视频 | 91精品成人久久 | 久久午夜鲁丝片 | 亚洲美女在线一区 | 九九欧美视频 | 成人毛片在线观看 | 岛国精品一区二区 | 婷婷精品 | 亚洲国产精彩中文乱码av | 在线观看黄色av | 91视频啊啊啊 | 国产无遮挡又黄又爽在线观看 | 日韩视频中文字幕在线观看 | www免费黄色 | 亚洲精品久久久久久国 | 国产九色在线播放九色 | 91九色在线播放 | 不卡的av在线 | 欧美日韩免费在线观看视频 | 一区二区三区四区精品 | 99精彩视频| 在线看黄网站 | 国产成人一区二区三区久久精品 | 成人av一级片 | 欧美另类一二三四区 | 在线观看亚洲精品 | 夜夜爱av| 免费色网| 中文字幕免费在线 | 久久夜色精品国产欧美乱极品 | 国产视频一区精品 | 色婷婷导航| 中文字幕视频网站 | 国产96在线视频 | 天天操天天操天天爽 | 天天操偷偷干 | 亚洲视频aaa| 美女黄频网站 | 亚洲欧美日韩国产一区二区 | 精品国产一区二区三区四 | 99在线免费视频观看 | 天天婷婷 | 色综合久久中文字幕综合网 | 日韩中文字幕视频在线 | 中文字幕丝袜 | 99久久精品久久久久久动态片 | 国产午夜一级毛片 | 久久精品aaa| 国产视频精选 | 久久av黄色| 91人人人 | 亚洲一级在线观看 | 免费能看的av | 婷婷久久一区二区三区 | 国产高清一| 在线观看国产一区二区 | 亚洲一区二区三区精品在线观看 | 精品在线观看一区二区 | 蜜臀av在线一区二区三区 | 国产第一二区 | 欧美在线aaa| 亚洲黄色免费电影 | 白丝av在线 | 日日婷婷夜日日天干 | 欧美xxxxx在线视频 | 久久精品女人毛片国产 | 999热线在线观看 | 亚洲香蕉在线观看 | 一区二区理论片 | 久久久久99999 | 日韩精品视频网站 | 国产精品麻豆三级一区视频 | 亚洲欧洲精品视频 | 亚洲日本欧美 | 免费av黄色| 婷婷视频在线播放 | 国内精品一区二区 | 日韩精品免费在线观看 | 免费看黄色91 | 国产精品一区二区久久精品爱微奶 | 久久国色夜色精品国产 | 91精品国产91热久久久做人人 | 免费在线激情电影 | 中文字幕在线播放av | 亚洲黄色小说网 | 中文乱码视频在线观看 | 少妇搡bbbb搡bbb搡69 | 91视频观看免费 | 99热最新在线 | 亚洲在线看 | 免费观看黄色12片一级视频 | 激情网站免费观看 | 在线成人小视频 | 欧美精品小视频 | 97香蕉久久超级碰碰高清版 | 91精品蜜桃 | 中文字幕 国产视频 | 日韩欧美在线观看一区二区三区 | 国产在线观看99 | 国产视频精品久久 | 香蕉视频4aa | 欧美午夜精品久久久久久浪潮 | 婷五月天激情 | 亚洲五月婷 | 综合网中文字幕 | a在线一区 | 国产精品久久久久久久久久免费看 | 国产91精品欧美 | av看片网址 | 久久国产精品99久久久久久老狼 | 天天色天天色天天色 | 91成人免费视频 | 国产特级毛片aaaaaa毛片 | 天堂av在线中文在线 | 国产无套视频 | 午夜精品久久久久久久99无限制 | 91精品综合在线观看 | 一区二区丝袜 | 好看的国产精品视频 | 久久久在线 | 国产日韩在线播放 | 午夜性福利 | 天天干,天天操 | 婷婷5月色 | 国产高清无av久久 | 天天操天天射天天添 | 激情五月婷婷综合网 | 91在线精品观看 | 天天狠狠 | 手机在线中文字幕 | 黄色软件在线观看视频 | 92国产精品久久久久首页 | 亚洲一区二区精品在线 | 三级动态视频在线观看 |