ByteArray 예제: .zip 파일 읽기

Adobe AIR 1.0 이상

이 예제에서는 다양한 유형의 여러 파일이 포함된 간단한 .zip 파일을 읽는 방법을 보여 줍니다. 이 예제에서는 각 파일을 ByteArray에 압축 해제하고 데스크톱에 쓰며 각 파일에 대한 메타데이터에서 관련 데이터를 추출합니다.

.zip 파일의 일반 구조는 http://www.pkware.com/documents/casestudies/APPNOTE.TXT 에서 유지 관리되는 PKWARE Inc. 사양을 기반으로 합니다. .zip 아카이브의 첫 번째 파일에 대한 파일 헤더와 파일 데이터가 먼저 오고 그 다음에는 각 추가 파일에 대한 파일 헤더와 파일 데이터 쌍이 옵니다. 파일 헤더의 구조는 나중에 설명하겠습니다. 다음으로 .zip 파일에는 선택적으로 데이터 설명자 레코드가 포함됩니다(일반적으로 출력 zip 파일이 디스크에 저장된 것이 아니라 메모리에 만들어진 경우). 그 다음으로는 아카이브 암호 해독 헤더, 아카이브 추가 데이터 레코드, 중앙 디렉토리 구조, 중앙 디렉토리 레코드의 Zip64 끝, 중앙 디렉토리 로케이터의 Zip64 끝 및 중앙 디렉토리 레코드의 끝과 같은 여러 추가 선택적 요소가 옵니다.

이 예제의 코드는 폴더가 포함되지 않은 zip 파일을 파싱하기 위한 용도로만 작성되었으며 데이터 설명자 레코드를 필요로 하지 않습니다. 마지막 파일 데이터 다음에 오는 모든 정보는 무시됩니다.

각 파일에 대한 파일 헤더의 형식은 다음과 같습니다.

파일 헤더 서명

4바이트

필요한 버전

2바이트

일반 용도의 비트 플래그

2바이트

압축 방법

2바이트(8=DEFLATE, 0=압축 해제)

가장 최근에 파일을 수정한 시간

2바이트

가장 최근에 파일을 수정한 날짜

2바이트

crc-32

4바이트

압축된 크기

4바이트

압축 해제된 크기

4바이트

파일 이름 길이

2바이트

추가 필드 길이

2바이트

파일 이름

variable

추가 필드

variable

파일 헤더 다음에는 실제 파일 데이터가 오며 이 데이터는 압축 방법 플래그에 따라 압축하거나 압축 해제할 수 있습니다. 파일 데이터를 압축 해제하는 경우 플래그는 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>

프로그램의 시작 부분에서는 다음 작업을 수행합니다.

  • 필요한 클래스 가져오기

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • 사용자 인터페이스 정의 정의

    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);
  • bytes ByteArray 정의

    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; 
  • .zip 파일을 나타낼 File( zfile ) 및 FileStream( zStream ) 객체 정의 및 파일을 추출할 .zip 파일(데스크톱 디렉토리에서 "HelloAIR.zip"이라는 파일)의 위치 지정

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

Flex에서 프로그램 코드는 init() 메서드에서 시작하며 이 메서드는 루트 mx:WindowedApplication 태그에 대한 creationComplete 핸들러로 호출됩니다.

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

이 프로그램은 READ 모드로 .zip 파일을 열어 작업을 시작합니다.

    zStream.open(zfile, FileMode.READ); 

그런 다음 bytes endian 속성을 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);

다음으로 이 코드는 30바이트 헤더의 첫 번째 바이트에서 정수( signature )를 읽습니다. ZIP 형식 정의는 모든 파일 헤더의 서명이 16진수 값 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 응용 프로그램에서만 작동합니다. AIR 및 Flash Player에 대해 Deflate 방식의 데이터 압축을 수행하려면 ByteArray의 inflate() 함수를 호출해야 합니다.

닫는 괄호는 outFile() 메서드를 제외하고 while 루프, init() 메서드 및 Flex 응용 프로그램 코드의 끝을 나타냅니다. 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(); 
}