В этом примере показано, как читать простой 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();
}