Voorbeeld van een bytearray: een ZIP-bestand lezen

Adobe AIR 1.0 of hoger

In dit voorbeeld ziet u hoe een eenvoudig ZIP-bestand wordt gelezen dat verschillende typen bestanden bevat. Hiervoor worden relevante gegevens uit de metagegevens van elk bestand opgehaald, waarbij elk bestand in een bytearray wordt gedecomprimeerd en naar de desktop wordt geschreven.

De algemene structuur van een ZIP-bestand is gebaseerd op de specificatie van PKWARE Inc., die u kunt vinden op http://www.pkware.com/documents/casestudies/APPNOTE.TXT . Eerst staan er een bestandsheader en bestandsgegevens voor het eerste bestand in het ZIP-bestand, gevolgd door een combinatie van een bestandsheader en bestandsgegevens voor elk ander bestand. (De structuur van de bestandsheader wordt later beschreven.) Daarna bevat het ZIP-bestand eventueel een record met een gegevensdescriptor (gewoonlijk wanneer het ZIP-bestand in het geheugen is gemaakt in plaats van opgeslagen op een schijf). Hierna volgen diverse optionele elementen: decoderingsheader van archief, extra gegevensrecord voor archief, centrale mapstructuur, Zip64-einde van centrale maprecord, Zip64-einde van centrale maplocator en het einde van de centrale maprecord.

De code in dit voorbeeld is alleen geschreven om ZIP-bestanden te parseren die geen mappen bevatten en er worden geen records met een gegevensdescriptor verwacht. Alle informatie na de laatste bestandsgegevens wordt genegeerd.

De bestandsheader voor elk bestand heeft de volgende indeling:

handtekening van bestandsheader

4 bytes

vereiste versie

2 bytes

algemene bitmarkering

2 bytes

compressiemethode

2 bytes (8=DEFLATE; 0=UNCOMPRESSED)

tijd van laatste bestandswijziging

2 bytes

datum van laatste bestandswijziging

2 bytes

crc-32

4 bytes

gecomprimeerde grootte

4 bytes

ongecomprimeerde grootte

4 bytes

lengte van bestandsnaam

2 bytes

lengte van extra veld

2 bytes

bestandsnaam

variabele

extra veld

variabele

Na de bestandsheader volgen de werkelijke bestandsgegevens die gecomprimeerd of niet kunnen zijn, afhankelijk van de markering voor de compressiemethode. De markering is 0 (nul) als de bestandsgegevens niet zijn gecomprimeerd, 8 als de gegevens zijn gecomprimeerd met de Deflate-algoritme of een andere waarde voor andere compressiealgoritmen.

De gebruikersinterface voor dit voorbeeld bestaat uit een label en een tekstgebied ( taFiles ). De toepassing schrijft de volgende informatie naar het tekstgebied voor elk bestand dat in het ZIP-bestand wordt aangetroffen: bestandsnaam, gecomprimeerde grootte en niet-gecomprimeerde grootte. Het volgende MXML-document bepaalt de gebruikersinterface voor de Flex-versie van de toepassing:

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

Aan het begin van het programma worden de volgende taken uitgevoerd:

  • De vereiste klassen worden geïmporteerd.

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • De gebruikersinterface wordt gedefinieerd. voor 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);
  • De bytearray bytes wordt gedefinieerd.

    var bytes:ByteArray = new ByteArray(); 
  • Er worden variabelen gedefinieerd voor het opslaan van metagegevens uit de bestandsheader.

    // 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; 
  • De objecten File ( zfile ) en FileStream ( zStream ) worden gedefinieerd voor het ZIP-bestand en de locatie wordt opgegeven van het ZIP-bestand waaruit de bestanden worden opgehaald: een bestand met de naam “HelloAIR.zip” in de desktopmap.

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

In Flex start de programmacode in de init() -methode, die wordt opgeroepen als de creationComplete -handler voor de root van de mx:WindowedApplication -tag.

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

Het programma begint met het openen van het ZIP-bestand in de leesmodus.

    zStream.open(zfile, FileMode.READ); 

Vervolgens wordt de eigenschap endian van bytes op LITTLE_ENDIAN ingesteld om aan te geven dat de minst significante byte eerst wordt aangegeven in de bytevolgorde van numerieke velden.

    bytes.endian = Endian.LITTLE_ENDIAN; 

Vervolgens wordt met de instructie while() een lus gestart die wordt uitgevoerd tot de huidige positie in de bestandsstroom groter is dan of gelijk is aan de grootte van het bestand.

    while (zStream.position < zfile.size) 
    {

Met de eerste instructie in de lus worden de eerste 30 bytes van de bestandsstroom ingelezen in de bytearray bytes . De eerste 30 bytes bevatten het gedeelte van de eerste bestandsheader dat een vaste grootte heeft.

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

Vervolgens wordt een geheel getal ( signature ) gelezen uit de eerste bytes van de header die uit 30 bytes bestaat. In de definitie van de ZIP-indeling is opgegeven dat de handtekening voor elke bestandsheader de hexadecimale waarde 0x04034b50 heeft; als de handtekening anders is, betekent dit dat het einde van het bestandsgedeelte van het ZIP-bestand is bereikt en dat er geen bestanden meer zijn om uit te pakken. In dat geval wordt de while -lus onmiddellijk beëindigd en wordt niet gewacht tot het einde van de bytearray is bereikt.

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

In het volgende deel van de code wordt de headerbyte op positie 8 gelezen en wordt de waarde opgeslagen in de variabele compMethod . Deze byte bevat een waarde die aangeeft met welke compressiemethode dit bestand is gecomprimeerd. Er zijn diverse compressiemethoden toegestaan, maar in de praktijk gebruiken bijna alle ZIP-bestanden de Deflate-compressiealgoritme. Als het huidige bestand is gecomprimeerd met Deflate-compressie, is compMethod 8; als het bestand niet is gecomprimeerd, is compMethod 0.

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

Na de eerste 30 bytes volgt een gedeelte van de header met een variabele lengte dat de bestandsnaam en eventueel een extra veld bevat. In de variabele offset is de grootte van dit gedeelte opgeslagen. De grootte wordt berekend door de lengte van de bestandsnaam en de lengte van het extra veld op te tellen. Deze waarden worden ingelezen uit de header op positie 26 en 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

Vervolgens wordt uit het gedeelte van de bestandsheader met variabele lengte het aantal bytes ingelezen dat is opgeslagen in de variabele offset .

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

De bestandsnaam wordt ingelezen vanuit het gedeelte van de header met variabele lengte en samen met de gecomprimeerde en niet-gecomprimeerde (oorspronkelijke) grootte van het bestand weergegeven in het tekstgebied.

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

In het voorbeeld wordt de rest van het bestand vanuit de bestandsstroom ingelezen in bytes voor de lengte die is opgegeven door de gecomprimeerde grootte, waarbij de bestandsheader in de eerste 30 bytes wordt overschreven. De gecomprimeerde grootte is nauwkeurig, zelfs als het bestand niet is gecomprimeerd, omdat de gecomprimeerde grootte in dat geval gelijk is aan de niet-gecomprimeerde grootte van het bestand.

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

Vervolgens wordt het gecomprimeerde bestand gedecomprimeerd en wordt de functie outfile() opgeroepen om het naar de uitvoerstroom te schrijven. De bestandsnaam en de bytearray die de bestandsgegevens bevat, worden doorgegeven aan outfile() .

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

In het eerder gegeven voorbeeld werkt bytes.uncompress(CompressionAlgorithm.DEFLATE) alleen in AIR-toepassingen. Als u verkleinde gegevens wilt decomprimeren voor zowel AIR als Flash Player, roept u de ByteArray-functie inflate() op.

De accolades sluiten geven het einde van de while -lus aan en van de init() -methode en de Flex-toepassingscode, behalve voor de outFile() -methode. De while -lus wordt opnieuw gestart en de volgende bytes in het ZIP-bestand worden verwerkt: er wordt een ander bestand uitgepakt of de verwerking van het ZIP-bestand wordt beëindigd als het laatste bestand is verwerkt.

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

Met de functie outfile() wordt in de schrijfmodus op de desktop een uitvoerbestand geopend dat de naam krijgt die is opgegeven met de parameter filename . Vervolgens wordt het gegevensbestand vanuit de parameter data naar de uitvoerstroom ( outStream ) geschreven en wordt het bestand gesloten.

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