Exemplo de ByteArray: leitura de um arquivo .zip

Adobe AIR 1.0 e posterior

Esse exemplo demonstra como ler um simples arquivo .zip contendo vários arquivos de diferentes tipos. Ele faz isso extraindo dados relevantes dos metadados para cada arquivo, descompactando cada arquivo em um ByteArray e escrevendo o arquivo na área de trabalho.

A estrutura geral de um arquivo .zip é baseada na especificação de PKWARE Inc., mantida em http://www.pkware.com/documents/casestudies/APPNOTE.TXT . Primeiramente, está o cabeçalho de um arquivo e os dados do arquivo para o primeiro arquivo no arquivo .zip, seguido por um cabeçalho de arquivo e par de dados de arquivo para cada arquivo adicional. (A estrutura do cabeçalho do arquivo é descrita posteriormente.) Em seguida, o arquivo .zip inclui opcionalmente um registro de descritor de dados (normalmente quando o arquivo zip de saída foi criado na memória, e não salvo em um disco). Em seguida, estão vários elementos opcionais adicionais: cabeçalho de descriptografia do arquivo, registro de dados extra do arquivo, estrutura de diretório central, final Zip64 de registro de diretório central, final Zip64 de localizador de diretório central e final de registro de diretório central.

O código nesse exemplo é escrito para analisar apenas arquivos zip que não contêm pastas e ele não espera registros de descritor de dados. Ele ignora todas as informações seguindo os dados do último arquivo.

O formato do cabeçalho do arquivo para cada arquivo é o seguinte:

assinatura do cabeçalho do arquivo

4 bytes

versão necessária

2 bytes

sinalizador de bits de propósito geral

2 bytes

método de compactação

2 bytes (8=DEFLATE; 0=UNCOMPRESSED)

última hora de modificação do arquivo

2 bytes

última data de modificação do arquivo

2 bytes

crc-32

4 bytes

tamanho compactado

4 bytes

tamanho descompactado

4 bytes

comprimento do nome do arquivo

2 bytes

comprimento de campo extra

2 bytes

nome de arquivo

variável

campo extra

variável

Seguindo o cabeçalho do arquivo estão os dados reais do arquivo, que podem ser compactados ou não, dependendo do sinalizador de método de compactação. O sinalizador é 0 (zero) se os dados do arquivo são descompactados, 8 se os dados são compactados usando o algoritmo DEFLATE ou outro valor para outros algoritmos de compactação.

A interface de usuário para esse exemplo consiste de um rótulo e uma área de texto ( taFiles ). O aplicativo escreve as seguintes informações na área de texto para cada arquivo encontrado no arquivo .zip: o nome de arquivo, o tamanho compactado e o tamanho descompactado. O documento MXML a seguir define a interface do usuário para a versão Flex do aplicativo:

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

O início do programa executa as seguintes tarefas:

  • Importa as classes necessárias

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • Define a interface de usuário para 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);
  • Define o ByteArray bytes

    var bytes:ByteArray = new ByteArray(); 
  • Define variáveis para armazenar metadados do cabeçalho do arquivo

    // 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; 
  • Define os objetos File ( zfile ) e FileStream ( zStream ) para representar o arquivo .zip e especifica o local do arquivo .zip a partir do qual os arquivos são extraídos — um arquivo chamado “HelloAIR.zip” no diretório da área de trabalho.

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

No Flex, o código do programa começa no método init() , chamado como manipulador creationComplete para a marca raiz mx:WindowedApplication .

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

O programa começa abrindo o arquivo .zip em modo READ.

    zStream.open(zfile, FileMode.READ); 

Ele, em seguida, define a propriedade endian de bytes para LITTLE_ENDIAN para indicar que a ordem de bytes de campos numéricos possui o byte menos significativo primeiro.

    bytes.endian = Endian.LITTLE_ENDIAN; 

Em seguida, uma instrução while() começa um loop que continua até que a posição atual no fluxo do arquivo seja maior que ou igual ao tamanho do arquivo.

    while (zStream.position < zfile.size) 
    {

A primeira instrução dentro do loop lê os primeiros 30 bytes do fluxo do arquivo no ByteArray bytes . Os primeiros 30 bytes formam a parte de tamanho fixo do cabeçalho do primeiro arquivo.

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

Em seguida, o código lê um inteiro ( signature ) dos primeiros bytes do cabeçalho de 30 bytes. A definição do formato ZIP especifica que a assinatura para cada cabeçalho de arquivo é o valor hexadecimal 0x04034b50 ; se a assinatura for diferente, significa que o código se moveu além da parte do arquivo .zip e não existem mais arquivos para extrair. Nesse caso, o código sai do loop while imediatamente, em vez de esperar pelo final da matriz de bytes.

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

A parte seguinte do código lê o byte do cabeçalho na posição de deslocamento 8 e armazena o valor na variável compMethod . Esse byte contém um valor que indica o método de compactação usado para compactar esse arquivo. Vários métodos de compactação são permitidos, mas, na prática, praticamente todos os arquivos .zip usam o algoritmo de compactação DEFLATE. Se o arquivo atual é compactado com a compactação DEFLATE, compMethod é 8; se o arquivo é descompactado, compMethod é 0.

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

Seguindo os primeiros 30 bytes está uma parte de comprimento variável do cabeçalho que contém o nome do arquivo e, possivelmente, um campo extra. A variável offset armazena o tamanho dessa parte. O tamanho é calculado pela adição do comprimento do nome do arquivo e o comprimento do campo extra, lido do cabeçalho nos deslocamentos 26 e 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

Em seguida, o programa lê a parte de comprimento variável do cabeçalho do arquivo para o número de bytes armazenados na variável offset .

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

O programa lê o nome do arquivo da parte de comprimento variável do cabeçalho e o exibe na área de texto, juntamente com os tamanhos compactados (zip) e descompactados (original) do arquivo.

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

O exemplo lê o resto do arquivo do fluxo de arquivos em bytes para o comprimento especificado pelo tamanho compactado, substituindo o cabeçalho do arquivo nos primeiros 30 bytes. O tamanho compactado é preciso, mesmo se o arquivo não está compactado, porque, nesse caso, o tamanho compactado é igual ao tamanho descompactado do arquivo.

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

Em seguida, o exemplo descompacta o arquivo compactado e chama a função outfile() para escrevê-la no fluxo de arquivos de saída. Ele transmite a outfile() o nome do arquivo e a matriz de bytes que contém os dados do arquivo.

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

No exemplo mencionado anteriormente, bytes.uncompress(CompressionAlgorithm.DEFLATE) funcionará somente em aplicativos do AIR. Para expandir os dados não compactados do AIR e do Flash Player, utilize a função de expansão inflate() .

As chaves de fechamento indicam o final do loop while e do método init() e do código do aplicativo Flex, exceto para o método outFile() . A execução volta ao início do loop while e continua processando os próximos bytes no arquivo .zip — extraindo outro arquivo ou finalizando o processamento do arquivo .zip se o último arquivo tiver sido processado.

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

A função outfile() abre um arquivo de saída no modo WRITE na área de trabalho, dando a ele o nome fornecido pelo parâmetro filename . Ela, em seguida, escreve os dados do arquivo do parâmetro data para o fluxo do arquivo de saída ( outStream ) e fecha o arquivo.

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