第五章、epub檔案處理 -- 解壓epub檔案
https://github.com/geometer/FBReaderJ
第五章、epub檔案處理 -- 解壓epub檔案
本章將介紹程式是如何將epub內部被壓縮過的xml檔案轉化為一個可以正常解析的char陣列。要想將檔案轉換成char陣列,就需要一個字元流類。而專門針對epub內部被xml檔案的字元流類需要一個專門的位元組流類ZLXMLParser類。獲取這種位元組流的工作就是由通過ZLZipEntryFile類的getInputStream方法來完成的。
在詳細介紹解壓流程之前,我們先簡略介紹一下zip檔案(epub檔案與zip檔案使用的是用一種壓縮格式)的解壓原理。
zip檔案內部的檔案和資料夾會有一個頭資訊(
緊跟在頭資訊之後的是檔案或資料夾的元資訊。元資訊中包括“壓縮狀態下大小”(Compressed size)、“解壓縮狀態下大小”(Uncompressed size)、和“檔名”(FileName)等資訊。而我們可以通過讀取epub檔案的位元組流得到epub檔案內部每個xml檔案的元資訊。
有了這些元資訊之後,當我們需要解析zip檔案內部中某一個xml檔案時,然後我們可以利用jni呼叫c語言寫的zlib庫,將epub檔案內部xml檔案對應的部分解壓成可以正常解析的xml檔案位元組流。
以上就是大致的解壓原理,如果大家還想再進一步瞭解相關資訊,有兩個網址可以推薦大家讀下。
http://blog.sina.com.cn/s/blog_4c3591bd0100zzm6.html
http://blog.csdn.net/zhoudaxia/article/details/8034606
http://www.cnblogs.com/esingchan/p/3958962.html
本章中涉及的主要方法有:
ZLZipEntryFile類:實現了getInputStream方法
ZipFile類內部InputStreamHolder介面實現類:獲得epub檔案的位元組流
LocalFileHeader類:這個類記錄了
ZipInputStream類:這個覆寫了InputStream類的read方法,這個方法將呼叫DeflatingDecompressor類。
DeflatingDecompressor類:這個類呼叫語言寫的zlib庫,將epub檔案位元組流中的一部分解壓還原成正常的xml檔案位元組流。
下面我們就開始詳細介紹程式碼:
ZLZipEntryFile類的getInputStream方法
getInputStream方法呼叫了兩個方法:
ZLZipEntryFile類的getZipFile方法,引數myParent是一個代表epub檔案的ZLPhysicalFile類
ZipFile類的getInputStream方法,引數myName是一個代表epub內部xml檔案的檔名(參考第二章“解析資原始檔”)
ZLZipEntryFile類的getZipFile方法
這個方法返回了一個ZipFile類,並實現了ZipFile類內的InputStreamHolder介面的getInputStream方法(52行)
因為getZipFile方法中的file引數是一個代表epub檔案的ZLPhysicalFile類,所以這個getInputStream方法會返回一個FileInputStream類(參考第二章“解析資原始檔”)
這個方法最終會返回了一個ZipFile類(63行)
ZipFile類的getInputStream方法
這個方法呼叫了同一個類內的getHeader方法
ZipFile類的getHeader方法
這個方法是要獲得一個LocalFileHeader類。這個LocalFileHeader類記錄了epub檔案內部的xml檔案具體從epub檔案位元組流哪個位置開始,壓縮狀態下佔據多少位元組以及解壓狀態下佔據多少位元組
LocalFileHeader類中的“DataOffset”屬性記錄了epub內部xml檔案代表了從位元組流的哪個位置開始,“CompressedSize”屬性記錄了這個xml檔案在壓縮狀態下在位元組流中佔據了多少位元組,“UncompressedSize”屬性則記錄了xml檔案在解壓狀態下會佔據多少位元組
getHeader方法呼叫了兩個方法,ZipFile類的getBaseStream方法與ZipFile類的readFileHeader方法
ZipFile類的getBaseStream方法
這個方法呼叫了MyBufferedInputStream類的建構函式,建構函式中又呼叫了ZipFile類內的InputStreamHolder介面實現類的getInputStream方法
InputStreamHolder介面的實現類在ZLZipEntryFile類的getZipFile方法中,我們已經介紹過了,會返回一個FileInputStream類。myFileInputStream屬性就會指向這個FileInputStream類。
ZipFile類的readFileHeader方法
這個方法會呼叫LocalFileHeader類裡面的readFrom方法
LocalFileHeader類的readFrom方法
readFrom方法呼叫了MyBufferedInputStream類的read4Bytes方法
read4Bytes方法呼叫了四遍MyBufferedInputStream類的read方法
MyBufferedInputStream類的read方法則最終呼叫了MyBufferedInputStream類中myFileInputStream屬性指向的FileInputStream類的read方法(myFileInputStream屬性在MyBufferedInputStream類的建構函式中設定,剛才已經介紹過)
返回ZipFile類的readFileHeader方法
程式碼根據epub檔案內部xml檔案的名字取出對應的LocalFileHeader類之後,以此為引數呼叫ZipFile類的createZipInputStream方法。這個方法帶又呼叫了一個ZipInputStream類的建構函式
返回ZipFile類的getHeader方法
LocalFileHeader類裡面的readFrom方法創建出來的LocalFileHeader類會被加入到myFileHeaders屬性指向的LinkedHashMap中。注意這裡LocalFileHeader類是以epub檔案內部的xml檔案的檔名(header.FileName)為鍵名加入到LinkedHashMap裡去的。
返回ZLZipEntryFile類的getInputStream方法
程式碼根據epub檔案內部xml檔案的名字取出對應的LocalFileHeader類之後,以此為引數呼叫ZipFile類的createZipInputStream方法。這個方法帶又呼叫了一個ZipInputStream類的建構函式
ZipInputStream類的建構函式
MyBufferedInputStream類的setPosition方法通過LocalFileHeader類中的DataOffset屬性將epub檔案的位元組流定位到內部xml檔案開始的位置
同時DeflatingDecompressor類的init方法則呼叫DeflatingDecompressor類的reset方法,將LocalFileHeader類中代表內部xml檔案在在壓縮狀態下在epub位元組流中佔據了多少位元組,在解壓狀態下會佔據多少位元組的資訊分別賦給了DeflatingDecompressor類內的屬性。
現在我們已經獲得了ZipInputStream類,這個類其實就是一個針對epub檔案內部被壓縮的xml檔案的位元組流類。我們在這一章的開篇曾經講過:“要想將檔案轉換成char陣列,就需要一個字元流類。而專門針對epub內部xml檔案的字元流類需要一個專門的位元組流類作為引數。”ZipInputStream就是這個專門的位元組流類。現在我們只需要在以特定位元組流類為引數,新建一個字元流類就完成任務了。而新建字元流類就是在ZLXMLParser類的建構函式中完成的。(ZLXMLParser類的呼叫流程請參見第六章)
下面,我們再來介紹下,字元流類將壓縮的xml檔案轉換成char陣列的過程。
ZipInputStream類中read方法
我們首先來複習下java中位元組流類和字元流類的關係。程式在呼叫字元流類讀取檔案的時候,其實底層還是在呼叫位元組流類在讀取。位元組流現將檔案轉換成byte陣列,字元流獲得這個byte陣列之後再呼叫CharsetDecoder這個類,根據字元編碼將byte陣列再轉換成相應的char陣列。在字元流將壓縮的xml檔案轉換成char陣列的過程中,CharsetDecoder這個類會根據utf-8的編碼將位元組流轉換成字元流。
我們需要關注的是FBReader是如何呼叫ZipInputStream類將壓縮的xml檔案轉換成byte陣列。這個其實就是ZipInputStream類中的read方法完成的事情。
ZipInputStream類的read方法會呼叫DeflatingDecompressor類的read方法填充作為引數傳進來的空byte陣列。
DeflatingDecompressor類的read方法中其實就是用myOutBuffer這個變數來填充傳進來的空byte陣列。
那麼這個myOutBuffer這個變數又是在什麼地方被填充的呢?就是在DeflatingDecompressor類的另一個無參read方法中。
這個無參read方法是在ZLXMLParser類的建構函式中被呼叫的,具體請參見第六章“epub檔案處理 -- 解析 container檔案與.opf檔案”中關於讀取xml檔案的三個核心類呼叫順序的介紹。
myOutBuffer屬性指向的byte陣列是在DeflatingDecompressor類中的fillOutBuffer方法被填充的。
fillOutBuffer方法中的myStream屬性所指向的epub檔案位元組流已經在ZipInputStream類的建構函式中被定位到了內部xml檔案開始的位置(請看對ZipInputStream建構函式的介紹)。
在fillOutBuffer方法中,再呼叫MyBufferedInputStream類的read方法時,就會直接從內部xml檔案開始的位置開始讀取,把xml檔案壓縮狀態下所佔用的位元組數全部讀取出來。
呼叫完MyBufferedInputStream類的read方法之後,通過jni呼叫的c++的inflate方法的所有引數就都被設定好了:myInBuffer引數代表內部xml檔案壓縮狀態下位元組流轉換成的byte陣列,myInBufferOffset引數為0,myInBufferLength引數代表內部xml檔案壓縮狀態下位元組流轉換成的byte陣列的長度,myOutBuffer引數代表存放內部xml檔案解壓狀態下位元組流的byte陣列。
在c++的inflate方法中,程式碼呼叫了zlib庫的inflate方法。然後為zlib庫裡的inflate方法設定了引數。
c++的inflate方法呼叫完畢,myOutBuffer屬性指向的byte陣列就填充完畢了。
位元組流填充完byte陣列之後,字元流會根據字元編碼再生成對應的char陣列。整個流程就完成了。