Przykład ByteArray: odczytywanie pliku .zip

Adobe AIR 1.0 i starsze wersje

W tym przykładzie przedstawiony jest sposób odczytywania pliku .zip zawierającego kilka plików różnego rodzaju. Następuje to poprzez pobranie istotnych danych z metadanych dla każdego pliku, rozpakowanie każdego pliku do obiektu ByteArray i zapisanie pliku na pulpicie.

Ogólna struktura pliku .zip jest oparta o specyfikację PKWARE Inc. dostępną pod adresem http://www.pkware.com/documents/casestudies/APPNOTE.TXT . W pierwszej kolejności w archiwum pliku .zip występuje nagłówek pliku, a następnie nagłówek pliku i pary danych pliku dla każdego dodatkowego pliku. (Struktura nagłówka pliku jest opisana w dalszej części). Następnie plik .zip opcjonalnie zawiera rekord deskryptora danych (zazwyczaj po utworzeniu wyjściowego pliku zip w pamięci, a nie po zapisaniu go na dysk). Dalej znajdują się dodatkowe opcjonalne elementy: nagłówek deskryptora archiwum, rekord dodatkowych danych archiwum, struktura katalogu centralnego, rekord końca katalogu rekord lokalizatora katalogu centralnego Zip64 oraz rekord końca rekordu katalogu centralnego.

W tym przykładzie kod jest zapisany wyłącznie w celu analizy plików zip, które nie zawierają folderów. Kod ten nie oczekuje rekordów deskryptora danych. Ignoruje wszystkie informacje występujące po danych ostatniego pliku.

Format nagłówka pliku dla każdego pliku jest następujący:

podpis nagłówka pliku

4 bajty

wymagana wersja

2 bajty

flaga bitowa ogólnego użytku

2 bajty

metoda kompresji

2 bajty (8=DEFLATE; 0=UNCOMPRESSED)

godzina ostatniej modyfikacji pliku

2 bajty

data ostatniej modyfikacji pliku

2 bajty

crc-32

4 bajty

skompresowany rozmiar

4 bajty

rozmiar bez kompresji

4 bajty

długość nazwy pliku

2 bajty

długość dodatkowego pola

2 bajty

nazwa pliku

zmienna

dodatkowe pole

zmienna

Właściwe dane pliku znajdują się po nagłówku pliku. Mogą być one skompresowane bądź nie w zależności od flagi metody kompresji. Flaga ma wartość 0 (zero), jeśli dane pliku nie są skompresowane, wartość 8, jeśli dane są skompresowane za pomocą algorytmu DEFLATE lub inną wartość w przypadku innych algorytmów kompresji.

Interfejs użytkownika w tym przykładzie składa się z etykiety i obszaru tekstowego ( taFiles ). Aplikacja zapisuje następujące informacje do obszaru tekstowego dla każdego pliku napotkanego w pliku .zip: nazwę pliku, skompresowany rozmiar i rozmiar bez kompresji. W następującym dokumencie MXML zdefiniowano interfejs użytkownika wersji aplikacji dla środowiska 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>

Na początku programu wykonywane są następujące zadania:

  • Importowanie wymaganych klas

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • Definiowanie interfejsu użytkownika dla programu 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);
  • Definiowanie obiektu ByteArray bytes

    var bytes:ByteArray = new ByteArray(); 
  • Definiowanie zmiennych w celu zapisania metadanych z nagłówka pliku

    // 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; 
  • Definiowanie obiektów File ( zfile ) i FileStream ( zStream ) w celu reprezentacji pliku .zip i określenie lokalizacji pliku .zip, z którego rozpakowywane są pliki — plik o nazwie „HelloAIR.zip” w katalogu pulpitu.

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

We Flex, kod programu zostaje uruchomiony w metodzie init() wywołanej jako moduł obsługi creationComplete dla głównego znacznika mx:WindowedApplication .

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

Program rozpoczyna się poprzez otwarcie pliku .zip w trybie odczytu.

    zStream.open(zfile, FileMode.READ); 

Następnie ustawiona zostaje właściwość endian obiektu bytes jako LITTLE_ENDIAN w celu wskazania, że kolejność bajtów pól numerycznych określa jako pierwsze bajty o najmniejszy znaczeniu.

    bytes.endian = Endian.LITTLE_ENDIAN; 

Następnie instrukcja while() rozpoczyna pętlę ciągnącą się do czasu, aż bieżące położenie w strumieniu plików będzie większe lub równe rozmiarowi pliku.

    while (zStream.position < zfile.size) 
    {

Pierwsza instrukcja w pętli odczytuje pierwsze 30 bajtów strumienia plików do obiektu bytes ByteArray. Pierwsze 30 bajtów tworzy pierwszą część nagłówka pliku o stałym rozmiarze.

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

Następnie kod odczytuje liczbę całkowitą ( signature ) z pierwszych bajtów 30-bajtowego nagłówka. Definicja formatu ZIP określa, że podpis dla każdego nagłówka pliku to wartość szesnastkowa 0x04034b50 ; jeśli podpis ma inną wartość, oznacza to, że kod został przeniesiony poza część pliku .zip i nie ma więcej plików do rozpakowania. W takim wypadku kod wychodzi bezpośrednio poza pętlę while i nie czeka na koniec tablicy bajtów.

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

Kolejna część kodu odczytuje bajt nagłówka w położeniu przesunięcia 8 i zapisuje wartość w zmiennej compMethod . Ten bajt zawiera wartość wskazującą na metodę kompresji użytą do skompresowania tego pliku. Dozwolonych jest kilka metod kompresji, jednak w praktyce prawie wszystkie pliki .zip korzystają z algorytmu kompresji DEFLATE. Jeśli bieżący plik zostanie skompresowany przy użyciu metody DEFLATE, wówczas zmienna compMethod będzie miała wartość 8; jeśli plik zostanie zdekompresowany, zmienna compMethod będzie miała wartość 0.

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

Za pierwszymi 30 bajtami znajduje się sekcja nagłówka o zmiennej długości, która zawiera nazwę pliku oraz dodatkowe pole (opcjonalnie). Zmienna offset zawiera rozmiar tej sekcji. Rozmiar jest obliczany poprzez dodanie długości nazwy pliku oraz długości pola dodatkowego, które są odczytywane przy przesunięciach 26 i 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

Następnie program odczytuje sekcję nagłówka pliku o zmiennej długości pod względem liczby bajtów zapisanych w zmiennej offset .

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

Program odczytuje nazwę pliku z sekcji nagłówka o zmiennej długości, a następnie wyświetla nazwę w obszarze tekstowym wraz z rozmiarem pliku skompresowanego (zip) i nieskompresowanego (oryginalnego).

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

W przykładzie przedstawiono odczytanie pozostałej części pliku ze strumienia pliku do bytes w celu określenia długości w pliku skompresowanym. W przykładzie następuje również zastąpienie nagłówka pliku w pierwszych 30 bajtach. Rozmiar skompresowany jest określony dokładnie, nawet jeśli plik nie jest skompresowany, ponieważ w tym przypadku wielkość pliku skompresowanego jest równa wielkości pliku nieskompresowanego.

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

Następnie w przykładzie dochodzi do dekompresji skompresowanego pliku oraz do wywołania funkcji outfile() w celu zapisania pliku w wyjściowym strumieniu pliku. Do funkcji outfile() przekazywana jest nazwa pliku oraz tablica bajtowa zawierająca dane pliku.

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

Obiekt bytes.uncompress(CompressionAlgorithm.DEFLATE) z poprzedniego przykładu będzie działać wyłącznie w aplikacjach AIR. Aby zdekompresować dla środowiska AIR i programu Flash Player dane, do których zastosowano algorytm deflate, należy wywołać metodę inflate() klasy ByteArray.

Nawias zamykający wskazuje koniec pętli while , koniec metody init() oraz kodu aplikacji Flex — z wyjątkiem metody outFile() . Wykonywanie rozpoczyna się od pętli while i kontynuuje przetwarzanie kolejnych bajtów w pliku .zip — wyodrębniając inny plik lub kończąc przetwarzanie pliku .zip po przetworzeniu ostatniego pliku.

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

Funkcja outfile() otwiera plik wyjściowy w trybie WRITE na pulpicie, nadając mu nazwę określoną przez parametr filename . Następnie zapisuje dane pliku z parametru data do wyjściowego strumienia pliku ( outStream ), a następnie zamyka plik.

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