2008년 10월 31일 금요일

java.util.zip 패키지에서 ZIP 압축 화일 형식을 처리하는 클래스의 사용법


다음 프로그램은 압축 화일을 지원하는 java.util.zip 패키지에서
.zip 압축 화일 형식 (다중 화일 압축 형식의 하나)을 처리하는 클래스의
사용법을 보여줍니다.
덧붙여, File 클래스를 사용하여 화일을 다루는 방법, 예외 클래스
중첩된 클래스, java.text 패키지를 이용한 날짜, 메씨지 형식화 출력
방법도 소개합니다.
현재, 이 패키지의 이해를 돕는 문서와 예제가 없어서 간단하게
만들어 보았습니다.


이 프로그램은 zip, unzip, WinZip 프로그램과 같은 일을 하도록
만들어져 있습니다만,
이 프로그램에서 사용된 FileOutputStream이나 FileInputStream을
URL 혹은 Socket 객체로부터 얻은 InputStream 혹은 OutputStream으로
바꾸어주면 네트워크상에서 여러 화일을 하나의 화일로 압축한 형태로
자료를 전송하고 받아서 처리하여, 네트워크 전송 속도를 향상시킬 수
있습니다.
일반 텍스트 자료를 압축하면 2 ~ 4배로 압축된다고 알려져 있습니다.
또한, 이 클래스들은 체크썸을 사용하여 저장, 전송 오류를 검사해줍니다.


이 프로그램이 압축한 화일을 WinZip이 제대로 처리하지 못한다면,
갖고 계신 WinZip 프로그램 버전에 버그가 있기 때문일 것입니다.
http://www.winzip.com 에서 최신 버전 (버전 6.3 Beta 4)을
다운로드받으실 수 있습니다.


- 테스트된 환경: JDK1.1.3 + 솔라리스, JDK1.1.3 + 윈도우즈


----------------- ZipTest.java --------------------------------------
// Usage: java ZipTest [ -r ] zipfile file1 [ file2 ... ]
import java.io.*;
import java.util.zip.*;


class ZipTest
{
static boolean isRecursive = false;
static ZipOutputStream zos;


public static void main( String[] args )
throws IOException
{
try
{ int argnum = 0;
if ( args[argnum].equals("-r") )
{ isRecursive = true;
argnum++;
}
File zipfile = new File( args[argnum++] );
if ( zipfile.exists() )
throw new Error( "ZIP 화일 " + zipfile + "이 이미 존재함." );
zos = new ZipOutputStream( new FileOutputStream( zipfile ) );
if ( args.length <= argnum )
throw new Error(
"사용법: java ZipTest [ -r ] zipfile file1 [ file2 ... ]" );
for(int i = argnum; i < args.length; ++i)
outputEntry( new File(args[i]) );
zos.close();
} catch( Error ex )
{ System.err.println( "오류: " + ex.getMessage() );
}
}

public static void outputEntry( File f ) throws Error, IOException
{ if ( ! f.exists() )
throw new Error( "화일 " + f + "이 존재하지 않음" );
String adjustedPath = f.getPath().replace(File.separatorChar, '/');
if ( f.isDirectory() && ! adjustedPath.endsWith("/") )
adjustedPath += '/';
ZipEntry entry = new ZipEntry( adjustedPath );
entry.setTime( f.lastModified() );
// File 클래스에서 lastModified() 메쏘드의 시각 기준이 문서화되어
// 있지는 않으나, 솔라리스와 윈도우즈에서는 Date 클래스와 같은
// 시각 기준이 사용되고 있음.
zos.putNextEntry( entry );

if ( f.isDirectory() )
{ System.out.println( "디렉토리 첨가: " + f );
if ( isRecursive )
{ String[] files = f.list();
for( int i = 0; i < files.length; ++i )
outputEntry( new File(f, files[i]) ); // 재귀적 호출 사용
}
} else
{ System.out.println( " 화일 첨가: " + f );
FileInputStream fis = new FileInputStream( f );
byte buf[] = new byte[1024];
for( int cnt; (cnt = fis.read(buf)) != -1; )
zos.write( buf, 0, cnt );
fis.close();
}
}


static class Error extends Exception
{ public Error(String mesg)
{ super(mesg); }
}
}
---------------------------------------------------------------------------------



----------------- UnzipTest.java --------------------------------------
// Usage: java UnzipTest [ -l ] zipfile
import java.io.*;
import java.util.zip.*;
import java.util.*;
import java.text.*;


class UnzipTest
{
public static void main( String[] args )
throws IOException
{
try
{
boolean isListing = false;
int argnum = 0;
if ( args[0].equals("-l") )
{ isListing = true;
argnum++;
}
File zipfile = new File( args[argnum] );
if ( ! zipfile.exists() )
throw new Error( "ZIP 화일 " + zipfile + "이 존재하지 않음." );
ZipInputStream zis
= new ZipInputStream( new FileInputStream( zipfile ) );
if ( isListing )
list(zis);
else
extract(zis);
System.out.close();
} catch( Error ex )
{ System.err.println( "오류: " + ex.getMessage() );
}
}


static void list(ZipInputStream zis) throws IOException
{
ZipEntry entry;
System.out.println( " 크기 (압축 크기) 날짜 시각 이름" );
System.out.println( " ---------------- ---- ---- ----" );
MessageFormat fmt = new MessageFormat(
"{0,date,MM-dd-yy} {0,time,HH:mm} {1}" );
while( (entry = zis.getNextEntry()) != null )
{ zis.closeEntry();
System.out.println(
padToWidth( String.valueOf(entry.getSize()), 7 )
+ padToWidth( "(" + String.valueOf(entry.getCompressedSize())
+ ")", 10 )
+ " " + fmt.format( new Object[] {
new Date( entry.getTime() ),
entry.getName() } )
);
}
}


static String padToWidth(String s, int width)
{ if ( s.length() >= width )
return s;
char padded[] = new char[width - s.length()];
for( int i = 0; i < padded.length; i++ )
padded[i] = ' ';
return padded + s;
}


static void extract(ZipInputStream zis) throws IOException, Error
{
ZipEntry entry;
while( (entry = zis.getNextEntry()) != null )
{ File entryFile
= new File( entry.getName().replace('/', File.separatorChar) );
if ( entry.isDirectory() )
{ if ( ! entryFile.exists() )
{ System.out.println( " 디렉토리 생성: " + entryFile );
entryFile.mkdirs();
}
continue;
}


if ( entryFile.getParent() != null )
{ File parent = new File( entryFile.getParent() );
if ( ! parent.exists() )
parent.mkdirs();
}
if ( entry.getMethod() == ZipEntry.STORED )
{ System.out.println( " 추출: " + entryFile );
} else if ( entry.getMethod() == ZipEntry.DEFLATED )
{ System.out.println( " 압축 풀기: " + entryFile );
} else
throw new Error( entryFile
+ "화일에서 처리할 수 없는 압축 방식이 사용되었음" );
if ( entryFile.exists() )
throw new Error( entryFile + "이 이미 존재함" );
FileOutputStream fos = new FileOutputStream( entryFile );
byte buf[] = new byte[1024];
for( int cnt; (cnt = zis.read(buf)) != -1; )
fos.write( buf, 0, cnt );
fos.close();
}
}


static class Error extends Exception
{ public Error(String mesg)
{ super(mesg); }
}
}
---------------------------------------------------------------------------------



----------- 실행 결과 ------------------------
C:\example\zip> mkdir test
C:\example\zip> copy *.java test
C:\example\zip> dir test
GZIPT~66 JAV 2,333 97-07-24 3:48 GZIPTest.java
UNZIP~U2 JAV 3,637 97-07-24 3:53 UnzipTest.java
ZIPTE~7G JAV 2,479 97-07-24 3:27 ZipTest.java
CHECK~Z6 JAV 1,674 97-07-22 6:11 CheckedStreamTest.java
C:\example\zip> java -Duser.timezone=JST ZipTest -r test.zip test
-------------------> workaround for JDK1.1 timezone bug
디렉토리 첨가: test
화일 첨가: test\GZIPTest.java
화일 첨가: test\CheckedStreamTest.java
화일 첨가: test\ZipTest.java
화일 첨가: test\UnzipTest.java
C:\example\zip> java -Duser.timezone=JST UnzipTest -l test.zip
크기 (압축 크기) 날짜 시각 이름
---------------- ---- ---- ----
0 (2) 07-25-97 01:15 test/
2333 (785) 07-24-97 03:48 test/GZIPTest.java
3637 (1243) 07-24-97 03:53 test/UnzipTest.java
2479 (1027) 07-24-97 03:27 test/ZipTest.java
1674 (586) 07-22-97 06:11 test/CheckedStreamTest.java
C:\example\zip> move test test2
C:\example\zip> java UnzipTest test.zip
디렉토리 생성: test\
압축 풀기: test\GZIPTest.java
압축 풀기: test\UnzipTest.java
압축 풀기: test\ZipTest.java
압축 풀기: test\CheckedStreamTest.java


(WinZip 프로그램과의 호환성 테스트)
C:\example\zip> move test test3
(WinZip에서 test.zip을 Extract한다)
C:\example\zip> dir test
GZIPT~66 JAV 2,333 97-07-24 3:48 GZIPTest.java
UNZIP~U2 JAV 3,637 97-07-24 3:53 UnzipTest.java
ZIPTE~7G JAV 2,479 97-07-24 3:27 ZipTest.java
CHECK~Z6 JAV 1,674 97-07-22 6:11 CheckedStreamTest.java
C:\example\zip> move test test4


(WinZip에서 test2.zip을 생성후, test2\*.*를 첨가)
C:\example\zip> java -Duser.timezone=JST UnzipTest -l test2.zip
크기 (압축 크기) 날짜 시각 이름
---------------- ---- ---- ----
2333 (785) 07-24-97 03:48 GZIPTest.java
3637 (1253) 07-24-97 03:53 UnzipTest.java
2479 (1031) 07-24-97 03:27 ZipTest.java
1674 (586) 07-22-97 06:11 CheckedStreamTest.java
C:\example\zip> mkdir test
C:\example\zip> cd test
C:\example\zip\test> set CLASSPATH=..
C:\example\zip\test> java UnzipTest ..\test2.zip
압축 풀기: GZIPTest.java
압축 풀기: UnzipTest.java
압축 풀기: ZipTest.java
압축 풀기: CheckedStreamTest.java
C:\example\zip\test> dir *.*
GZIPT~66 JAV 2,333 97-07-25 1:28 GZIPTest.java
UNZIP~U2 JAV 3,637 97-07-25 1:28 UnzipTest.java
ZIPTE~7G JAV 2,479 97-07-25 1:28 ZipTest.java
CHECK~Z6 JAV 1,674 97-07-25 1:28 CheckedStreamTest.java

댓글 없음:

댓글 쓰기