ByteArray 範例:讀取 .zip 檔Adobe AIR 1.0 以及更新的版本 本範例會說明如何讀取含有多個不同類型檔案的單純 .zip 檔。其做法是從每個檔案的中繼資料內擷取相關資料、將每個檔案解壓縮到 ByteArray,再將結果檔案輸出至桌面。 .zip 檔的一般結構是以 PKWARE Inc. 的規格為準,這項規格詳列於 http://www.pkware.com/documents/casestudies/APPNOTE.TXT 中。其開頭為 .zip 封存內第一個檔案的檔案檔頭和檔案資料,且後面接著其它每個檔案的檔案檔頭和檔案資料配對組合 (檔案檔頭的結構將於稍後說明)。接著,.zip 檔可能會包含資料描述項記錄 (通常是在輸出的 zip 檔建立於記憶體而非儲存至磁碟機時)。後續則是幾個其它的選擇性元素:封存解密檔頭、封存額外資料記錄、中央目錄結構、中央目錄記錄的 Zip64 尾端、中央目錄定位器的 Zip64 尾端,以及中央目錄記錄的尾端。 本範例的程式碼僅用來剖析不含資料夾的 zip 檔,同時假設並無資料描述項記錄。位於最終檔案資料後面的各項資訊將全數忽略。 每個檔案的檔案檔頭格式如下:
接續在檔案檔頭後面的部分就是實際檔案資料,可能已壓縮或未壓縮 (視壓縮方法旗標而定)。旗標為 0 (零) 代表檔案資料未壓縮,8 代表資料使用 DEFLATE 演算法進行壓縮,其它值代表使用其它壓縮演算法。 本範例的使用者介面會包含一個標籤和一個文字區域 (taFiles)。應用程式會將 .zip 檔中所找到每個檔案的下列資訊寫入文字區域:檔案名稱、壓縮後大小與解壓縮後大小。下列 MXML 文件定義 Flex 版本應用程式的使用者介面: <?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();"> <mx:Script> <![CDATA[ // The application code goes here ]]> </mx:Script> <mx:Form> <mx:FormItem label="Output"> <mx:TextArea id="taFiles" width="320" height="150"/> </mx:FormItem> </mx:Form> </mx:WindowedApplication> 本程式一開始會執行下列工作:
在 Flex 中,首先執行的程式碼是 init() 方法,此方法會做為 mx:WindowedApplication 根標籤的 creationComplete 處理常式來呼叫。 // for Flex private function init():void { 首先,程式會以 READ 模式開啟 .zip 檔。 zStream.open(zfile, FileMode.READ); 接著,將 bytes 的 endian 屬性設為 LITTLE_ENDIAN,以指示數值欄位的位元組順序為最低位元組在前。 bytes.endian = Endian.LITTLE_ENDIAN; 之後,while() 陳述式將起始迴圈反覆執行,直到檔案串流中的目前位置大於或等於檔案大小為止。 while (zStream.position < zfile.size) { 迴圈內的第一個陳述式會將檔案串流開頭的 30 個位元組讀入 ByteArray bytes。這 30 個開頭位元組構成了第一個檔案檔頭的固定大小部分。 // read fixed metadata portion of local file header zStream.readBytes(bytes, 0, 30); 接著,程式碼自此 30 位元組檔頭的開頭位元組中讀取整數 (signature)。依 ZIP 格式規格定義,每個檔案檔頭的簽名都是十六進位值 0x04034b50;如果簽名並非此值,即表示程式碼執行已超出 .zip 檔的檔案部分,也就是說檔案全都擷取完畢。在此情況下,程式碼會立刻結束 while 迴圈,而不再繼續讀到位元組陣列結尾。 bytes.position = 0; signature = bytes.readInt(); // if no longer reading data files, quit if (signature != 0x04034b50) { break; } 程式碼的下一段落則指定偏移 8 個單位,以便讀取該位置的檔頭位元組,並將其值儲存在 compMethod 變數中。包含於此位元組的值指出用於壓縮該檔案的壓縮方法。允許的壓縮方法有很多種,但實際上幾乎所有 .zip 檔都會使用 DEFLATE 壓縮演算法。如果目前的檔案使用 DEFLATE 壓縮方法進行壓縮,compMethod 即為 8;若檔案未壓縮,則 compMethod 為 0。 bytes.position = 8; compMethod = bytes.readByte(); // store compression method (8 == Deflate) 接續在開頭 30 個位元組後面的是長度不定的檔頭部分,內含檔案名稱,甚至還有額外欄位。這個部分的大小會儲存在 offset 變數中。大小的計算方式則是將檔案名稱長度和額外欄位長度相加 (分別讀取自檔頭偏移位置 26 和 28)。 offset = 0; // stores length of variable portion of metadata bytes.position = 26; // offset to file name length flNameLength = bytes.readShort(); // store file name offset += flNameLength; // add length of file name bytes.position = 28; // offset to extra field length xfldLength = bytes.readShort(); offset += xfldLength; // add length of extra field 之後,程式會讀取長度不定的檔案檔頭部分,而該部分的位元組數目即等於 offset 變數的值。 // read variable length bytes between fixed-length header and compressed file data zStream.readBytes(bytes, 30, offset); 程式也將從長度不定的檔頭部分讀取檔案名稱,連同檔案壓縮後 (zipped) 與解壓縮後 (原始) 大小一併顯示在文字區域內。 // Flash version bytes.position = 30; fileName = bytes.readUTFBytes(flNameLength); // read file name taFiles.appendText(fileName + "\n"); // write file name to text area bytes.position = 18; compSize = bytes.readUnsignedInt(); // store size of compressed portion taFiles.appendText("\tCompressed size is: " + compSize + '\n'); bytes.position = 22; // offset to uncompressed size uncompSize = bytes.readUnsignedInt(); // store uncompressed size taFiles.appendText("\tUncompressed size is: " + uncompSize + '\n'); // Flex version bytes.position = 30; fileName = bytes.readUTFBytes(flNameLength); // read file name taFiles.text += fileName + "\n"; // write file name to text area bytes.position = 18; compSize = bytes.readUnsignedInt(); // store size of compressed portion taFiles.text += "\tCompressed size is: " + compSize + '\n'; bytes.position = 22; // offset to uncompressed size uncompSize = bytes.readUnsignedInt(); // store uncompressed size taFiles.text += "\tUncompressed size is: " + uncompSize + '\n'; 本範例接著會將目前檔案仍在檔案串流中的其餘部分 (壓縮後大小指定此長度) 讀入 bytes 以覆寫開頭 30 個位元組的檔案檔頭。即使檔案未壓縮,壓縮後大小還是很準確,因為這類檔案在壓縮前後的大小會相等。 // read compressed file to offset 0 of bytes; for uncompressed files // the compressed and uncompressed size is the same zStream.readBytes(bytes, 0, compSize); 範例的下一步是將壓縮過的檔案進行解壓縮,然後呼叫 outfile() 函數,將結果檔案寫入輸出檔案串流。傳遞給 outfile() 的參數指定了檔案名稱,以及包含檔案資料的位元組陣列。 if (compMethod == 8) // if file is compressed, uncompress { bytes.uncompress(CompressionAlgorithm.DEFLATE); } outFile(fileName, bytes); // call outFile() to write out the file 右大括弧用來表示 while 迴圈、init() 方法以及 Flex 應用程式程式碼的結尾 (outFile() 方法除外)。當返回 while 迴圈起始處再次執行迴圈時,將繼續處理 .zip 檔中的下一輪位元組;可能是擷取另一個檔案,或因檔案都已處理完畢而停止處理 .zip 檔。 } // end of while loop } // for Flex version, end of init() method and application outfile() 函數將在桌面上以 WRITE 模式開啟輸出檔,檔案名稱則是由 filename 參數提供。接著,再將 data 參數提供的檔案資料寫入輸出檔案串流 (outStream),最後關閉該檔案。 // Flash version function outFile(fileName:String, data:ByteArray):void { var outFile:File = File.desktopDirectory; // destination folder is desktop outFile = outFile.resolvePath(fileName); // name of file to write var outStream:FileStream = new FileStream(); // open output file stream in WRITE mode outStream.open(outFile, FileMode.WRITE); // write out the file outStream.writeBytes(data, 0, data.length); // close it outStream.close(); } private function outFile(fileName:String, data:ByteArray):void { var outFile:File = File.desktopDirectory; // dest folder is desktop outFile = outFile.resolvePath(fileName); // name of file to write var outStream:FileStream = new FileStream(); // open output file stream in WRITE mode outStream.open(outFile, FileMode.WRITE); // write out the file outStream.writeBytes(data, 0, data.length); // close it outStream.close(); } |
|