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();
}
|
|