BLOG ARTICLE Dev Note/Java | 8 ARTICLE FOUND

  1. 2008.03.04 Java SE를 이용하여 Zip 파일 생성하기
  2. 2008.03.03 Java SE의 정규 표현식

Dev Note/Java 2008.03.04 23:16



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에 추가한다.

Dev Note/Java 2008.03.03 23:28

Java SE의 정규 표현식(expression or regex)의 지원은 1.4 이후 자바 플랫품의 일부가 되어 왔다. java.util.regex package의 클래스는 펄 언어가 제공하는 것과 유사하게 패턴 매칭을 지원하지만 자바 언어구문 및 클래스를 사용한다.1.4 에서는 Pattern, Matcher, PatternSyntaxException 3가지 클래스가 있고, 1.5에서는 MatchResult 인터페이스가 추가되었다.

정규 표현식은 간단하게 Pattern과 Matcher를 사용한다. Pattern 클래스를 이용하여 정규 표현식을 정의하고, Matcher 클래스를 사용하여 입력 소스에 대한 패턴 매칭을 검사한다. 표현식에서 패턴에 구문 오류가 있으면 PatternSyntaxException이 발생된다.

-- 정규 표현식 사용 예
Pattern pattern = Pattern.compile(<regular expression>);
Matcher matcher = pattern.matcher(<input source>);

while(matcher.find()) {
    System.out.printf"Found: \"%s\" from %d to %d.%n", matcher.group(), matcher.start(), matcher.end());
}

-- 정규 표현식 사용 예제 프로그램
다음 코드는 기본적인 정규 표현식 프로그램으로 사용자가 정규 표현식과 비교 문자열을 입력하도록 하며, 입력 된 값을 이용하여 패턴 매칭되는 것을 출력한다.
import java.util.regex.*;

public class Regex {

    public static void main(String args[]) {
        Console console = System.console();

        // Get regular expression
        String regex = console.readLine("%nEnter expression: ");
        Pattern pattern = Pattern.compile(regex);

        // Get source
        String source = console.readLine("Enter input source: ");
        Matcher matcher = pattern.matcher(source);

        // Show matches
       while (matcher.find()) {
           System.out.printf("Found: \"%s\" from %d to %d.%n", matcher.group(), matcher.start(), matcher.end());
        }
    }
}

그러면 정규 표현식의 모양이 어떻게 되는지를 알아보자.
- Pattern 클래스는 보다 세부적인 사항을 제공하지만 기본적으로 정규 표현식은 다른 문자 시퀀스와 일치시킬 문자 시퀀스이다.
  예를 들어, "Hello, World" 문자열에서 개의 L("ll") 문자열 리터럴 패턴을 찾을 있다. 앞의 프로그램은 시작 위치 2 위치 4에서 "ll" 패턴을 찾을 것이다.
- 정규 표현식 패턴은 특수 메타 문자를 포함할 수 있다. 메타 문자는 정규 표현식에서 강력한 매칭 기능을 제공한다. 정규 표현식에서는 "([{\^-$|]})?*+."의 15문자를 메타 문자로 사용할 수 있다.

* 특수 메타 문자 *
대괄호([ ]) :
대활호 안의 문자 중 하나가 텍스트에서 발견되는 경우 매칭이 성공하는 일련의 문자를 지정할 수 있다. 단일 문자를 매칭하는 데만 사용된다.
개발 문자의 매칭 이외에 대괄호 문자([ ])를 사용하여 [j-z]로 지정된 j-z의 문자처럼 일정 범위의 문자를 매칭할 수 있다. 이러한 문자 범위는
"foo[j-z]"처럼 문자열 리터럴과 결합할 수도 있다.
[a-zA-Z]처럼 여러 범위를 결합하여 a에서 z 사이의 소문자와 대문자를 나타낼 수도 있다.
  "co[cl]a" 패턴은 coca 및 cola라는 단어와 매칭된다. cocla는 매칭되지 않는다.
 
"foo[j-z]" 패턴은 fool 단어와 매칭된다. food는 매칭되지 않는다.
^ : 문자열 리터럴 또는 문자 범위의 제외를 지정한다.
  "foo[^j-z]" 패턴은 foo로 시작하고 j에서 z
사이의 문자로 끝나지 않는 단어를 찾는다. food라는 문자열이 매칭에 성공하며, fool은 매칭되지 않는다.
마침표(.) : 임의 문자를 나타내는 데 지정된다.
  ".oney" 패턴은 money honey와 매칭되며 oney로 끝나는 5자의 어느 단어와도 매칭된다.
역슬래쉬(\) :
다른 문자와 함께 사용되어 전체 문자 집합이 지정된다. 숫자 집합을 나타내기 위해 [0-9]를 사용할 수 있지만 \d를 사용할 수도 있다. 숫자가 아닌 문자 집합을 나타내기 위해 [^0-9]를 사용할 수도 있다. 또는 \D의 미리 정의된 문자 클래스 문자열을 사용할 수 있다.
 
\s (whitespace), \S (non-whitespace), \w (word character [a-zA-Z0-9]), \W (non-word character), \p{Punct} (punctuation), \p{Lower} (lowercase [a-z]), \p{Upper} (uppercase [A-Z])
  자바 소스에서는
String regexString = "\\s"; 처럼 이스케이프해야 한다.
수량자({횟수}) : 동일 패턴의 반복 매칭을 지정한다. "[a-z][a-z][a-z]" 패턴을 사용할 수도 있지만 그럴 ":[a-z]{3}"으로 나타낼 수 있다. 수량자를 사용할 때 범위도 지정할 수 있다. {3}은 정확히 3번을 의미하지만 {3,}은 적어도 3번을 의미한다. 수량자 {3, 5}3번에서 5번까지의 패턴
물음표(?), 0, 더하기(+) : 수량자와 함께 사용되면서 회수를 특정 숫자가 아닌 0번 또는 한 번(?), 0번 이상(0), 한 번 이상(+)을 나타낸다.
  [a-z]? 패턴은 a-z의 문자와 0번 또는 한 번 매칭된다. [a-z]* 패턴은 a-z의 문자와 0번 이상 매칭된다. [a-z]+ 패턴은 a-z의 문자와 한 번 이상 매칭된다.

Java SE 정규식에 대한 자세한 내용을 살펴보려면 자바 온라인 자습서의 정규 표현식 편을 참고하면 된다.