ByteArray 示例:读取 .zip 文件

Adobe AIR 1.0 和更高版本

该示例演示了如何读取包含若干不同类型文件的简单 .zip 文件。读取过程如下:从每个文件的元数据中提取相关数据,将每个文件解压缩至 ByteArray 并将该文件写入桌面。

.zip 文件的一般结构基于 PKWARE Inc. 的规范(此规范位于 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;如果该数据为使用 DEFLATE 算法压缩的数据,则为 8;如果采用的是其他压缩算法,则为其他值。

该示例的用户界面由一个标签和一个文本区域 ( 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>

该示例的用户界面由一个标签和一个文本区域 ( taFiles ) 组成。该应用程序将它在 zip 文件中遇到的各文件的以下信息写入文本区域:文件名、压缩后的大小和解压缩后的大小。以下 HTML 页定义了该应用程序的用户界面:

<html> 
    <head> 
        <style type="text/css"> 
            #taFiles 
            { 
                border: 1px solid black; 
                font-family: Courier, monospace; 
                white-space: pre; 
                width: 95%; 
                height: 95%; 
                overflow-y: scroll; 
            } 
        </style> 
        <script type="text/javascript" src="AIRAliases.js"></script> 
        <script type="text/javascript"> 
            // The application code goes here 
        </script> 
    </head> 
    <body onload="init();"> 
        <div id="taFiles"></div> 
    </body> 
</html>

程序开头将执行以下任务:

  • 定义 bytes ByteArray

    var bytes = new air.ByteArray();
  • 定义用来存储文件标头中元数据的变量

    // variables for reading fixed portion of file header 
    var fileName = new String(); 
    var flNameLength; 
    var xfldLength; 
    var offset; 
    var compSize; 
    var uncompSize; 
    var compMethod; 
    var signature; 
     
    var output;
  • 定义用来表示 .zip 文件的 File ( zfile ) 和 FileStream ( zStream ) 对象,并指定将从中提取文件的 .zip 文件的位置(桌面目录下名为“HelloAIR.zip”的文件)。

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

在 Flex 中,程序代码从 init() 方法开始,该方法将作为根 mx:WindowedApplication 标签的 creationComplete 处理函数调用。

程序代码通过 init() 方法启动,该方法将作为 body 标签的 onload 事件处理函数调用。

function init() 
{

该程序将首先以 READ 模式打开 .zip 文件。

    zStream.open(zfile, air.FileMode.READ);

然后将 bytes endian 属性设置为 LITTLE_ENDIAN ,以指示数字字段的字节顺序为最低有效字节位于最前。

    bytes.endian = air.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);

接下来,代码将从这 30 字节标头最前面的字节中读取一个整数 ( signature )。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);

程序将从标头的可变长度部分中读取文件名并在文本区域中显示它,同时显示文件压缩后(已压缩)及解压缩后(原始)大小。

        bytes.position = 30;  
        fileName = bytes.readUTFBytes(flNameLength); // read file name 
        output += fileName + "<br />"; // write file name to text area 
        bytes.position = 18; 
        compSize = bytes.readUnsignedInt();  // store size of compressed portion 
        output += "\tCompressed size is: " + compSize + '<br />'; 
        bytes.position = 22;  // offset to uncompressed size 
        uncompSize = bytes.readUnsignedInt();  // store uncompressed size 
        output += "\tUncompressed size is: " + uncompSize + '<br />';

该示例从文件流中将文件的其余部分按照压缩后大小所指定的长度读入到 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(air.CompressionAlgorithm.DEFLATE); 
        } 
        outFile(fileName, bytes);   // call outFile() to write out the file 

右括号指示 while 循环、 init() 方法以及应用程序代码结束,不过 outFile() 方法除外。执行过程再次返回至 while 循环的开始处并继续处理 .zip 文件中其余的字节:提取另一个文件,如果最后一个文件已处理完毕,则终止该 .zip 文件处理。当所有文件均经过处理后,示例将 output 变量的内容写入 div 元素 taFiles 以便在屏幕上显示文件信息。

    } // end of while loop 
     
    document.getElementById("taFiles").innerHTML = output; 
} // end of init() method

outfile() 函数将以 WRITE 模式打开桌面上的输出文件,为其指定 filename 参数提供的名称。然后该函数将把文件数据从 data 参数中写入输出文件流 ( outStream ) 并关闭该文件。

function outFile(fileName, data) 
{ 
    var outFile = air.File.desktopDirectory; // dest folder is desktop 
    outFile = outFile.resolvePath(fileName);  // name of file to write 
    var outStream = new air.FileStream(); 
    // open output file stream in WRITE mode 
    outStream.open(outFile, air.FileMode.WRITE); 
    // write out the file 
    outStream.writeBytes(data, 0, data.length); 
    // close it 
    outStream.close(); 
}