Java SE를 이용하여 Zip 파일 생성에 대해 알아보자. ZipOutputStream 및 java.util.zip package에 대해 구체 적으로 알아본다.
Zip 파일의 생성을 살펴보기 전에 Zip 파일의 목적을 간단히 알아보면 Zip 파일은 패키징 메커니즘을 제공하여 여러 파일을 하나의 파일로 묶을 수 있게 한다. 여러 파일을 단일 파일로 패키징하여 웹 다운 및 관리가 가능하며, 패키징 파일은 디렉토리 계층구조 같은 추가 정보를 포함할 수 있으므로 압축을 풀 때 어플리케이션 또는 시리즈의 자원에 필요한 경로를 유지할 수 있다.
이 글에서는 Zip 파일 사용의 세가지 측면, Zip 파일 생성, Zip 파일에 파일 추가, 해당 추가 파일 압축에 대해 다룬다.
우선 ZIP 파일을 생성할 때는 ZIP 스트림이 생성된다. ZipOutputStream은 송신 바이트 압축을 위한 스트림을 제공한다. 다른 OutputStream을 받는 ZipOutputStream의 단일 구성자가 있다.
public ZipOutputStream(OutputStream out)
구성자 인수가 FileOutputStream 유형이면 ZipOutputStream이 작성한 압축 바이트는 파일에 저장된다. 하지만 ZipOutputStream을 파일과 관련해서만 사용할 수 있는 것은 아니다. 소켓 연결 또는 기타 일부 비파일 중심 스트림으로부터 나오는 OutputStream을 사용할 수도 있다. 따라서 파일 중심 ZipOutputStream의 경우 일반적인 사용법은 다음과 같다.
String path = "afile.zip";
FileOutputStream fos = new FileOutputStream(path);
ZipOutputStream zos = new ZipOutputStream(fos);
생성된 후에는 ZipOutputStream에 바이트를 작성하는 것으로 충분하지 않다. 대신 출력 스트림을 구성요소의 컬렉션으로 다루어야 한다. ZipOutputStream의 각 구성요소는 ZipEntry와 쌍을 이룬다. 이 ZipEntry를 생성하고 ZipOutputStream에 추가한 후에 해당 컨텐츠를 스트림에 실제로 작성해야 한다.
String name = ...;
ZipEntry entry = new ZipEntry(name);
zos.putNextEntry(entry);
zos.write(<< all the bytes for entry >>);
각 엔트리는 전체 스트림에서 마커 역할을 하며 라이브러리 파일에서 엔트리와 관련된 바이트를 찾을 수 있다. ZIP 파일이 생성된 후 엔트리 컨텐츠를 도로 가져와야 할 경우 관련 입력 스트림을 요청하기만 하면 된다.
ZipFile zip = "afile.zip";
ZipEntry entry = zip.getEntry("anEntry.name");
InputStream is = zip.getInputStream(entry);
ZIP 파일을 생성하고 해당 파일에 엔트리를 추가하는 방법을 살펴본 이 시점에서 java.util.zip 라이브러리가 ZipOutputStream의 추가 엔트리에 대해 일정 수준의 제어를 제공한다는 것을 지적할 필요가 있다. 먼저, 엔트리를 ZipOutputStream에 추가하는 순서는 엔트리가 .zip 파일에 실제로 위치하는 순서이다. ZipFile의 entries() 메소드에 의해 반환된 엔트리의 열거를 조작하여 영문자 또는 크기 순서대로 목록을 생성할 수도 있지만 엔트리는 출력 스트림에 작성된 순서대로 저장되어 있다.
ZIP/JAR 파일에 추가된 파일은 개별적으로 압축된다. 라이브러리 패키지를 전체적으로 압축하는 Microsoft CAB 파일과 달리, ZIP/JAR 파일 내의 파일들은 각각 별도로 압축되거나 압축되지 않는다. ZipOutputStream에 ZipEntry를 추가하기 전에 해당 연관 바이트의 압축 여부를 결정해야 한다. ZipEntry의 setMethod 메소드를 사용하면 두 가지 사용 가능 압축 형식 중에서 어떤 형식을 사용할지 지정할 수 있다. 압축되지 않은 파일을 원하면 ZipEntry의 STORED 상수를 사용하고, 압축된 버전을 원하면 DEFLATED 설정을 사용한다. 압축 효율성은 제어할 수 없다. 이는 연관 엔트리의 데이터 유형에 좌우된다. 일반 텍스트는 원래 크기의 80% 정도까지 쉽게 압축할 수 있지만 MP3 또는 JPEG 데이터의 압축률은 이보다 현저히 떨어진다.
모든 파일을 압축하는 게 당연하다고 생각할 수도 있지만 파일을 압축하고 푸는 데에는 시간이 소요된다. 생성 시점에서 압축 작업에 너무 많은 시간과 자원이 소요될 경우 전체 파일 데이터를 STORED 형식으로 저장하여 원시 바이트를 저장하는 것이 나은 경우가 있다. 압축 해제의 경우에도 마찬가지이다. 물론, 압축되지 않은 파일은 용량이 더 크므로 파일 전송 시 더 많은 대역폭을 사용하고 더 많은 디스크 공간을 차지하므로 이에 따른 비용을 지불해야 한다. ZipFile 전체가 아니라 각 엔트리에 대한 설정을 변경해야 한다는 것을 유념한다. 하지만 각 엔트리에 대해 각기 다른 설정을 사용하는 것보다 전체 ZipFile을 압축 또는 비압축하는 것이 보다 일반적이다.
압축 방법으로 STORED 상수를 사용할 경우 알아 두어야 할 한 가지 사항이 있다. 엔트리가 압축될 때 자동으로 설정되는 ZipEntry의 특정 속성을 명시적으로 설정해야 한다. 해당 속성은 엔트리의 입력 스트림에 대한 체크섬, 압축 크기 및 크기이다. 입력 파일인 경우 크기 및 압축 크기는 바로 파일 크기가 될 수 있다. 체크섬을 계산하려면 ava.util.zip 패키지의 CRC32 클래스를 사용한다. checksum 값을 무시하기 위해 단순히 0 또는 -1을 전달할 수는 없다. ZIP 파일을 작성하고 나중에 해당 파일을 읽을 때 입력사항을 검증하기 위해 CRC 값이 사용된다.
ZipEntry entry = new ZipEntry(name);
entry.setMethod(ZipEntry.STORED);
entry.setCompressedSize(file.length());
entry.setSize(file.length());
CRC32 crc = new CRC32();
crc.update(<< all the bytes for entry >>);
entry.setCrc(crc.getValue()); zos.putNextEntry(entry);
첨부된 Java 프로그램은 STORED 압축 메소드를 사용하여 일련의 파일을 결합한다. 프로그램의 첫 번째 인수는 생성할 ZIP 파일이 된다. 나머지 인수는 추가할 파일을 나타낸다. 생성할 ZIP 파일이 이미 존재하면 프로그램은 파일을 수정하지 않고 종료된다. 존재하지 않는 파일을 ZIP 파일에 추가하는 경우 프로그램은 존재하지 않는 파일을 건너뛰고 나머지 명령줄 인수를 생성된 ZIP에 추가한다.