本範例會說明如何讀取含有多個不同類型檔案的單純 .zip 檔。其做法是從每個檔案的中繼資料內擷取相關資料、將每個檔案解壓縮到 ByteArray,再將結果檔案輸出至桌面。
.zip 檔的一般結構是以 PKWARE Inc. 的規格為準,這項規格詳列於
http://www.pkware.com/documents/casestudies/APPNOTE.TXT
中。其開頭為 .zip 封存內第一個檔案的檔案檔頭和檔案資料,且後面接著其它每個檔案的檔案檔頭和檔案資料配對組合 (檔案檔頭的結構將於稍後說明)。接著,.zip 檔可能會包含資料描述項記錄 (通常是在輸出的 zip 檔建立於記憶體而非儲存至磁碟機時)。後續則是幾個其它的選擇性元素:封存解密檔頭、封存額外資料記錄、中央目錄結構、中央目錄記錄的 Zip64 尾端、中央目錄定位器的 Zip64 尾端,以及中央目錄記錄的尾端。
本範例的程式碼僅用來剖析不含資料夾的 zip 檔,同時假設並無資料描述項記錄。位於最終檔案資料後面的各項資訊將全數忽略。
每個檔案的檔案檔頭格式如下:
檔案檔頭簽名
|
4 個位元組
|
必要版本
|
2 個位元組
|
一般用途位元旗標
|
2 個位元組
|
壓縮方法
|
2 個位元組 (8=DEFLATE;0=未壓縮)
|
檔案上次修改時間
|
2 個位元組
|
檔案上次修改日期
|
2 個位元組
|
crc-32
|
4 個位元組
|
壓縮後大小
|
4 個位元組
|
解壓縮後大小
|
4 個位元組
|
檔案名稱長度
|
2 個位元組
|
額外欄位長度
|
2 個位元組
|
檔案名稱
|
變數
|
額外欄位
|
變數
|
接續在檔案檔頭後面的部分就是實際檔案資料,可能已壓縮或未壓縮 (視壓縮方法旗標而定)。旗標為 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>
本程式一開始會執行下列工作:
-
匯入所需的類別
import flash.filesystem.*;
import flash.utils.ByteArray;
import flash.events.Event;
-
定義使用者介面 (Flash 適用)
import fl.controls.*;
//requires TextArea and Label components in the Library
var taFiles = new TextArea();
var output = new Label();
taFiles.setSize(320, 150);
taFiles.move(10, 30);
output.move(10, 10);
output.width = 150;
output.text = "Contents of HelloAir.zip";
addChild(taFiles);
addChild(output);
-
定義
bytes
ByteArray
var bytes:ByteArray = new ByteArray();
-
定義變數以儲存檔案檔頭提供的中繼資料
// variables for reading fixed portion of file header
var fileName:String = new String();
var flNameLength:uint;
var xfldLength:uint;
var offset:uint;
var compSize:uint;
var uncompSize:uint;
var compMethod:int;
var signature:int;
-
定義 File (
zfile
) 和 FileStream (
zStream
) 物件以代表 .zip 檔,並指定該 .zip 檔的位置 (即 "HelloAIR.zip" 檔案,位於桌面目錄) 以從中擷取檔案。
// File variables for accessing .zip file
var zfile:File = File.desktopDirectory.resolvePath("HelloAIR.zip");
var zStream:FileStream = new FileStream();
在 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
if (compSize == 0) continue;
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
在上述範例中,
bytes.uncompress(CompressionAlgorithm.DEFLATE)
只能在 AIR 應用程式中使用。若要為 AIR 和 Flash Player 解壓縮採用 deflate 壓縮的資料,請叫用 ByteArray 的
inflate()
函數。
右大括弧用來表示
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();
}