ByteArray の例:.zip ファイルの読み取り

Adobe AIR 1.0 およびそれ以降

この例では、種類の異なる複数のファイルを含む単純な .zip ファイルを読み取る方法を示します。その手段として、ここでは各ファイルに関連するデータをメタデータから抽出し、ファイルごとに ByteArray に圧縮解除して、ファイルをデスクトップに書き込んでいます。

.zip ファイルの一般的な構造は、http://www.pkware.com/documents/casestudies/APPNOTE.TXT に記載されている PKWARE Inc. の仕様に基づきます。最初にあるのは .zip アーカイブ内の最初のファイルのファイルヘッダーとファイルデータで、その後に、他の各ファイルのファイルヘッダーとファイルデータのペアが続きます。ファイルヘッダーの構造については後で説明します。.zip ファイルには次に、必要に応じてデータ記述子レコードが含まれます(通常は、出力 zip ファイルがディスクに保存されずメモリに作成された場合がこれに当たります)。次に、アーカイブ復号化ヘッダー、アーカイブ追加データレコード、中央ディレクトリ構造、Zip64 中央ディレクトリ終了レコード、Zip64 中央ディレクトリ終了ロケーター、中央ディレクトリ終了レコードなど、いくつかの追加オプションエレメントがあります。

この例のコードは、フォルダーを含まない zip ファイルのみを解析するように作られており、データ記述子レコードは想定していません。最後のファイルデータの後にある情報はすべて無視します。

各ファイルのファイルヘッダーの形式は次のとおりです。

ファイルヘッダーシグネチャ

4 バイト

必要なバージョン

2 バイト

汎用ビットフラグ

2 バイト

圧縮方法

2 バイト(8 = DEFLATE、0 = UNCOMPRESSED)

最終ファイル更新時刻

2 バイト

最終ファイル更新日

2 バイト

crc-32

4 バイト

圧縮サイズ

4 バイト

非圧縮サイズ

4 バイト

ファイル名の長さ

2 バイト

追加フィールドの長さ

2 バイト

ファイル名

variable

追加フィールド

variable

ファイルヘッダーの後には実際のファイルデータがあり、圧縮方法フラグに応じて、圧縮されている場合とされていない場合があります。フラグの値は、ファイルデータが圧縮されていない場合は 0(ゼロ)、データが DEFLATE アルゴリズムで圧縮されている場合は 8、他の圧縮アルゴリズムの場合は別の値になります。

この例のユーザーインターフェイスは、ラベルとテキスト領域(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);
  • ByteArray の bytes を定義します。

    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; 
  • .zip ファイルを表す File(zfile)オブジェクトと FileStream(zStream)オブジェクトを定義し、展開するファイルが含まれている .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 
{

プログラムは、.zip ファイルを READ モードで開くことにより開始します。

    zStream.open(zfile, FileMode.READ); 

次に、bytesendian プロパティを 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 形式の定義では、すべてのファイルヘッダーのシグネチャは 16 進数値 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);

そして、ヘッダーの可変長部分からファイル名を読み取り、ファイルの圧縮サイズ(zip サイズ)と非圧縮サイズ(元のサイズ)と共にテキスト領域に表示します。

// 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 の両方用に圧縮されていない deflatede のデータを取得するには、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(); 
}