ByteArray-Beispiel: Lesen von .zip-Dateien

Adobe AIR 1.0 und höher

In diesem Beispiel wird gezeigt, wie einfache .zip-Dateien, die mehrere Dateien unterschiedlicher Dateitypen enthalten, gelesen werden. Hierfür werden die relevanten Daten aus den Metadaten jeder Datei extrahiert, jede Datei wird in ein ByteArray dekomprimiert und die Datei wird auf den Desktop geschrieben.

Die allgemeine Struktur von .zip-Dateien baut auf den Vorgaben von PKWARE Inc. auf, die Sie unter http://www.pkware.com/documents/casestudies/APPNOTE.TXT finden. Zuerst kommen der Datei-Header und die Dateidaten für die erste Datei im .zip-Archiv, gefolgt vom Datei-Header und den Daten der folgenden Dateien. (Die Struktur des Headers wird weiter unten beschrieben.) Als Nächstes enthält die .zip-Datei einen optionalen Datendeskriptordatensatz (in der Regel, wenn die .zip-Datei im Arbeitsspeicher erstellt und nicht auf einem Datenträger gespeichert wurde). Dem folgen verschiedene weitere optionale Elemente: archive decryption header, archive extra data record, central directory structure, Zip64 end of central directory record, Zip64 end of central directory locator und end of central directory record.

Der Code in diesem Beispiel wurde so geschrieben, dass nur .zip-Dateien, die keine Ordner enthalten, analysiert werden. Datendeskriptordatensätze werden nicht erwartet. Sämtliche Informationen, die auf die letzten Dateidaten folgen, werden ignoriert.

Der Datei-Header der einzelnen Dateien folgt dem Format:

Datei-Header-Signatur

4 Byte

Erforderliche Version

2 Byte

Allgemeines Bit-Flag

2 Byte

Komprimierungsverfahren

2 Byte (8=DEFLATE; 0=UNCOMPRESSED)

Letzte Änderung Uhrzeit

2 Byte

Letzte Änderung Datum

2 Byte

crc-32

4 Byte

Komprimierte Dateigröße

4 Byte

Entkomprimierte Dateigröße

4 Byte

Dateinamenlänge

2 Byte

Zusatzfeldlänge

2 Byte

Dateiname

Variable

Feldlänge

Variable

Auf den Datei-Header folgen die eigentlichen Dateidaten, die je nach Komprimierungsverfahren-Flag komprimiert oder entkomprimiert sein können. Das Flag steht auf 0 (Null), wenn die Datei nicht komprimiert ist, auf 8, wenn die Daten mit dem deflate-Algorithmus komprimiert wurden, und auf einem anderen Wert für andere Komprimierungsalgorithmen.

Die Benutzeroberfläche für dieses Beispiel besteht aus einer Beschriftung und einem Textbereich ( taFiles ). Die Anwendung schreibt die folgenden Informationen in den Textbereich jeder Datei, die sie in der .zip-Datei findet: Dateiname, komprimierte Dateigröße und entkomprimierte Dateigröße. Das folgende MXML-Dokument definiert die Benutzeroberfläche für die Flex-Version der Anwendung:

<?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>

Zu Beginn des Programms werden folgende Aufgaben ausgeführt:

  • Importieren der erforderlichen Klassen

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • Definition der Benutzeroberfläche für 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);
  • Definition des ByteArrays bytes

    var bytes:ByteArray = new ByteArray(); 
  • Definition der Variablen für das Speichern von Metadaten aus dem Datei-Header

    // 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; 
  • Definition der File-Objekte ( zfile ) und FileStream-Objekte ( zStream ) für die Darstellung der .zip-Datei und Angabe des Speicherorts der .zip-Datei, aus der die Dateien extrahiert werden - eine Datei mit dem Namen „HelloAIR.zip” im Desktop-Verzeichnis.

    // File variables for accessing .zip file 
    var zfile:File = File.desktopDirectory.resolvePath("HelloAIR.zip"); 
    var zStream:FileStream = new FileStream();

In Flex beginnt der Programmcode mit der Methode init() , die als creationComplete -Prozedur für das Stamm-Tag mx:WindowedApplication aufgerufen wird.

// for Flex 
private function init():void 
{

Das Programm beginnt mit dem Öffnen der .zip-Datei im Lesemodus.

    zStream.open(zfile, FileMode.READ); 

Es setzt dann die Eigenschaft endian von bytes auf LITTLE_ENDIAN , um festzulegen, dass bei der Byte-Reihenfolge numerischer Felder das niederwertigste Byte zuerst angegeben wird.

    bytes.endian = Endian.LITTLE_ENDIAN; 

Dann startet eine while() -Anweisung eine Schleife, die fortgesetzt wird, bis die aktuelle Position im Datenstrom der Dateigröße entspricht oder diese überschreitet.

    while (zStream.position < zfile.size) 
    {

Die erste Anweisung in der Schleife liest die ersten 30 Byte des Dateistroms in das ByteArray bytes . Diese ersten 30 Byte bilden den ersten Datei-Header, dessen Größe vorgegeben ist.

        // read fixed metadata portion of local file header 
        zStream.readBytes(bytes, 0, 30);

Danach liest der Code eine Ganzzahl ( signature ) aus den ersten Byte des 30-Byte-Headers. Durch die ZIP-Formatdefinition wird für die Signatur jedes Datei-Headers der Hexadezimalwert 0x04034b50 vorgegeben. Weicht die Signatur hiervon ab, ist der Code über den Dateiteil der .zip-Datei hinausgekommen und es sind keine weiteren Dateien zu extrahieren. In diesem Fall verlässt der Code die Schleife while sofort und wartet nicht auf das Ende des Byte-Arrays.

        bytes.position = 0; 
        signature = bytes.readInt(); 
        // if no longer reading data files, quit 
        if (signature != 0x04034b50) 
        { 
            break; 
        }

Der nächste Abschnitt des Codes liest den Header-Byte an Offset-Position 8 und speichert den Wert in der Variablen compMethod . Dieses Byte enthält einen Wert, der das Komprimierungsverfahren angibt, das zur Komprimierung der Datei angewendet wurde. Es sind verschiedene Komprimierungsverfahren zulässig, praktisch verwenden jedoch nahezu alle .zip-Dateien den Komprimierungsalgorithmus „deflate“. Wurde die aktuelle Datei mit „deflate“ komprimiert, ist compMethod 8; ist die Datei nicht komprimiert, ist compMethod 0.

        bytes.position = 8; 
        compMethod = bytes.readByte();  // store compression method (8 == Deflate)

Den ersten 30 Byte folgt ein Header-Bestandteil von variabler Länge, der den Dateinamen und möglicherweise ein zusätzliches Feld enthält. In der Variablen offset ist die Größe dieses Bestandteils gespeichert. Die Größe wird durch Addition der Dateinamenlänge und der Länge des Zusatzfelds berechnet, die vom Header an den Offsets 26 und 28 eingelesen werden.

        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

Als Nächstes liest das Programm den Bestandteil des Datei-Headers mit variabler Länge ein, der die Anzahl der in der Variablen offset gespeicherten Byte angibt.

        // read variable length bytes between fixed-length header and compressed file data 
        zStream.readBytes(bytes, 30, offset);

Das Programm liest den Dateinamen aus dem Bestandteil des Datei-Headers mit variabler Länge und zeigt diesen im Textbereich zusammen mit der komprimierten (gezippten) und entkomprimierten (ursprünglichen) Größe der Datei an.

// 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'; 

Im Beispiel wird der Rest der Datei für die durch die komprimierte Größe angegebene Länge aus dem Dateistrom in bytes gelesen und der Datei-Header in den ersten 30 Byte überschrieben. Die Angabe für die komprimierte Größe gilt auch dann, wenn die Datei nicht komprimiert wurde, da die komprimierte Größe in diesem Fall der entkomprimierten Größe der Datei entspricht.

    // 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);

Im Beispiel wird dann die komprimierte Datei entkomprimiert und es wird die Funktion outfile() aufgerufen, um sie in den Ausgabedatenstrom zu schreiben. outfile() , der Dateiname und das Byte-Array, das die Dateidaten enthält, werden übergeben.

        if (compMethod == 8) // if file is compressed, uncompress 
        { 
            bytes.uncompress(CompressionAlgorithm.DEFLATE); 
        } 
        outFile(fileName, bytes);   // call outFile() to write out the file

Im vorigen Beispiel funktioniert bytes.uncompress(CompressionAlgorithm.DEFLATE) nur in AIR-Anwendungen. Um mit „deflate“ komprimierte Daten für AIR und Flash Player zu dekomprimieren, rufen Sie die inflate() -Funktion von ByteArray auf.

Die geschlossenen Klammern weisen auf das Ende der while -Schleife, der init() -Methode und des Flex-Anwendungscodes hin, mit Ausnahme der outFile() -Methode. Die Ausführung kehrt zum Beginn der Schleife while zurück und fährt mit der Verarbeitung der nächsten Byte in der .zip-Datei fort. Es wird eine weitere Datei extrahiert oder die Verarbeitung der .zip-Datei wird nach Verarbeitung der letzten Datei abgeschlossen.

    } // end of while loop 
} // for Flex version, end of init() method and application

Die Funktion outfile() öffnet eine Ausgabedatei im Schreibmodus auf dem Desktop und versieht sie mit dem durch den Parameter filename angegebenen Namen. Dann schreibt die Funktion die Dateidaten aus dem Parameter data in den Ausgabedatenstrom ( outStream ) und schließt die Datei.

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