Пример объекта ByteArray: чтение zip-файла

Adobe AIR 1.0 и более поздних версий

В этом примере показано, как читать простой zip-файл, содержащий несколько файлов разных типов. Для этого информация извлекается из метаданных каждого файла, каждый файл распаковывается в ByteArray и записывается на рабочий стол.

Общая структура zip-файла основана на спецификации компании PKWARE, опубликованной по адресу http://www.pkware.com/documents/casestudies/APPNOTE.TXT . Сначала идет заголовок файла и данные первого файла в zip-архиве, затем заголовок и данные второго файла и т. д. (Структура заголовка файла будет рассмотрена немного позднее). Кроме того, zip-файл может содержать запись дескриптора данных (как правило, если итоговый zip-файл был сохранен в память, а не на диск). Также включены несколько дополнительных элементов: заголовок дескриптора архива, запись дополнительных данных архива, структура центрального каталога, конец записи центрального каталога в формате Zip64, локатор центрального каталога в формате Zip64, конец записи центрального каталога.

Код в этом примере подходит только для разбора zip-файлов, не содержащих папок, и ему не требуются записи дескрипторов данных. Никакая информация после данных последнего файла в списке не учитывается.

Формат заголовка файла для каждого таков:

подпись заголовка файла

4 байта

требование к версии

2 байта

флажок бита общего назначения

2 байта

метод сжатия

2 байта (8=DEFLATE; 0=UNCOMPRESSED)

время последнего изменения файла

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);
  • Определение 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; 
  • Определение объектов 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() , вызываемого в качестве обработчика события creationComplete для корневого тега mx:WindowedApplication .

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

Программа начинается с открытия zip-файла в режиме READ.

    zStream.open(zfile, FileMode.READ); 

Затем свойству endian объекта bytes задается значение 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);

Затем код считывает целое число ( signature ) из первых байтов 30-байтового заголовка. В определении 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);

Программа читает имя файла из части заголовка с переменной длиной и отображает его в текстовой области вместе с данными о размере файла в сжатом и несжатом виде.

// 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. Чтобы распаковать данные, сжатые с использованием алгоритма deflate, и для AIR, и для Flash Player, вызовите функцию inflate() класса ByteArray.

Закрывающие скобки указывают на окончание цикла 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(); 
}