JAVA.UTIL.SCANNER로 텍스트 스캔하기

JAVA.UTIL.SCANNER로 텍스트 스캔하기

J2SE 5.0에는 일상적 태스크를 좀 더 쉽게 구현할 수 있도록 하는 클래스와 메소드들이 추가되었다. 이번 팁에서는 새로 추가된 java.util.Scanner클래스를 이용함으로써 일반 표현문을 사용하는 스트링과 프리미티브 타입을 읽고 파싱(parsing)하는 것이 어떻게 좀 더 쉬워졌는지 알아보도록 하자.

J2SE 5.0의 출시 이전에는 파일에서 텍스트를 읽으려면 다음의 TextReader 클래스 같은 코드를 작성해야했다.


  1. import java.io.BufferedReader;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.File;
  5.  
  6.  public class TextReader {
  7.   private static void readFile(String fileName) {
  8.     try {
  9.       File file = new File(fileName);
  10.       FileReader reader = new FileReader(file);
  11.       BufferedReader in = new BufferedReader(reader);
  12.       String string;
  13.       while ((string = in.readLine()) != null) {
  14.         System.out.println(string);
  15.       }
  16.       in.close();
  17.     } catch (IOException e) {
  18.       e.printStackTrace();
  19.     }
  20.   }
  21.  
  22.   public static void main(String[] args) {
  23.     if (args.length != 1) {
  24.       System.err.println("usage: java TextReader "
  25.         + "file location");
  26.       System.exit(0);
  27.     }
  28.     readFile(args[0]);
  29.   }
  30. }

이와 같은 클래스에서의 기본적인 접근법은 하드 드라이브의 실제 파일과 일치하는 File 오브젝트를 생성하는 것이다. 그리고 나서 그 파일과 관련된 FileReader와 그 FileReaderBufferedReader를 생성하고, 그 후 BufferedFile 리더를 사용하여 한번에 한 줄씩 읽는다.

실행되는 TextReader클래스를 보기위해서는 클래스에 대한 문서를 생성하여 읽고 파싱해야한다. 문서를 생성하기 위해서는 TextReader와 같은 디렉토리 안에 있는 TextSample.txt라는 파일에 다음과 같은 두 줄의 텍스트를 저장해야 한다.

   Here is a small text file that you will
   use to test java.util.scanner.

TextReader를 컴파일하고 다음을 입력하여 구동시켜보자.

   java TextReader TextSample.txt

표준 출력으로 되돌아온 원본 파일을 보게 될 것이다.

프리미티브 타입과 스트링을 파싱하는 클래스인 java.util.Scanner를 이용하여 TextReader의 코드를 간단하게 할 수 있다.


  1. import java.io.File;
  2. import java.io.FileNotFoundException;
  3. import java.util.Scanner;
  4.  
  5. public class TextScanner {
  6.  
  7.   private static void readFile(String fileName) {
  8.     try {
  9.       File file = new File(fileName);
  10.       Scanner scanner = new Scanner(file);
  11.       while (scanner.hasNext()) {
  12.         System.out.println(scanner.next());
  13.       }
  14.       scanner.close();
  15.     } catch (FileNotFoundException e) {
  16.       e.printStackTrace();
  17.     }
  18.   }
  19.  
  20.   public static void main(String[] args) {
  21.     if (args.length != 1) {
  22.       System.err.println("usage: java TextScanner1"
  23.         + "file location");
  24.       System.exit(0);
  25.     }
  26.     readFile(args[0]);
  27.   }
  28. }

TextScanner를 컴파일하고 다음과 같이 구동하자.

   java TextScanner TextSample.txt

다음과 같은 결과가 나타난다.

   Here
   is
   a
   small
   text
   file
   that
   you
   will
   use
   to
   test
   java.util.scanner.

TextScanner 는 파일로부터 Scanner 오브젝트를 생성한다. Scanner는 파일의 컨텐츠를 구획자 패턴을 이용하여 분해한다. 구획자 패턴의 디폴트 값은 흰 여백이다. 그 후 TextScannerScannerhasNext() 메소드를 호출한다. 이 메소드는 Scanner 입력값에 파일의 마지막 부분에 이를 때까지 다른 token이 있으면 'true'를 리턴한다. next() 메소드는 다음 token을 나타내는 스트링을 리턴한다. 따라서 TextScanner는 파일의 마지막부분에 이를 때까지 각 라인에서 next()에 의해 리턴되는 스트링을 프린트한다.

ScanneruseDelimiter 를 이용해 입력물을 토큰화하는 데 이용하는 구획자를 변경시킬 수도 있다. 메소드에 스트링 또는 java.util.regex.Pattern에 전달해주면 된다. 어떤 패턴들이 적절한 지에 대해서는 JavaDocs page for Pattern를 참조하기 바란다. 예를 들어 newline(\n)을 구획자로 이용하여 한번에 한 줄의 입력물을 읽을 수 있다. 다음은 새줄 문자를 구획자로 이용하는 수정된 readFile() 메소드이다.


  1.    private static void readFile(String fileName) {
  2.      try {
  3.        Scanner scanner = new Scanner(new File(fileName));
  4.        scanner.useDelimiter
  5.          (System.getProperty("line.separator"));
  6.        while (scanner.hasNext()) {
  7.          System.out.println(scanner.next());
  8.        scanner.close();
  9.      } catch (FileNotFoundException e) {
  10.        e.printStackTrace();
  11.      }
  12.    }

마지막 줄을 찾는 다른 옵션들도 있다. 예를 들어 새줄 문자로 끝나는 라인이나 캐리지 리턴(enter키)과 newline으로 끝나는 라인들을 조사할 수 있다. "\r\n|\n" 일반 표현문을 이용하여 이를 실행할 수 있다. java.util.regex.Pattern의 JavaDocs는 또다른 라인 종결기들을 보여주므로 좀 더 복잡한 분석은 "\r\n|[\r\n\u2028\u2029\u0085]"표현문을 이용한다. 또한 Scanner 클래스의 hasNextLine()nextLine() 메소드를 이용할 수 있다. 어느 경우이던 수정된 TextScanner를 사용하면 결과물은 TextSample.txt의 컨텐츠와 레이아웃에 부합될 것이다. 다음을 참고하기 바란다.

   Here is a small text file that you will
   use to test java.util.scanner.

Scanner에 의해 사용된 구획자의 패턴을 간단하게 변경하여 큰 효과와 유연성을 얻을 수 있다. 예를 들어 다음의 구획자를 지정하면,

   scanner.useDelimiter("\\z");

한번에 전체 파일을 읽는다. 이는 Pat Niemeyer가 java.net blog에서 제안하고 있는 요령과도 비슷하다. 몇 개의 중간 오브젝트를 생성하지 않고도 웹페이지의 모든 컨텐츠를 읽을 수 있는 것이다. 다음 WebPageScanner클래스의 코드는 java.net homepage의 현재 컨텐츠를 읽고 있다.


  1.    import java.net.URL;
  2.    import java.net.URLConnection;
  3.    import java.io.IOException;
  4.    import java.util.Scanner;
  5.  
  6.    public class WebPageScanner {
  7.      public static void main(String[] args) {
  8.        try {
  9.          URLConnection connection =
  10.            new URL("http://java.net").openConnection();
  11.          String text = new Scanner(
  12.            connection.getInputStream()).
  13.            useDelimiter("\\Z").next();
  14.        } catch (IOException e) {
  15.          e.printStackTrace();
  16.        }
  17.      }
  18.    }

Scanner 클래스로 스트링 이외의 것들도 다룰 수 있다. 프리미티브 타입으로 이루어진 데이터를 파싱하는 데에도 사용할 수 있다. 이에 대한 예제로, 다음의 세 라인을 Employee.data라는 이름의 파일(TextSample와 같은 디렉토리 안)에 저장하자.

   Joe, 38, true
   Kay, 27, true
   Lou, 33, false

이를 하나의 큰 스트링으로 취급하여 이 스트링을 파싱한 후에 대화문을 실행할 수도 있지만, 대신에 이 파일을 두가지 단계로 파싱해보자. 이는 다음의 클래스 DataScanner에 설명되어 있다.


  1.    import java.util.Scanner;
  2.    import java.io.File;
  3.    import java.io.FileNotFoundException;
  4.  
  5.    public class DataScanner {
  6.  
  7.      private static void readFile(String fileName) {
  8.        try {
  9.          Scanner scanner =
  10.            new Scanner(new File(fileName));
  11.          scanner.useDelimiter
  12.            (System.getProperty("line.separator"));
  13.          while (scanner.hasNext()) {
  14.            parseLine(scanner.next());
  15.          }
  16.          scanner.close();
  17.        } catch (FileNotFoundException e) {
  18.          e.printStackTrace();
  19.        }
  20.      }
  21.  
  22.      private static void parseLine(String line) {
  23.        Scanner lineScanner = new Scanner(line);
  24.       lineScanner.useDelimiter("\\s*,\\s*");
  25.        String name = lineScanner.next();
  26.        int age = lineScanner.nextInt();
  27.        boolean isCertified = lineScanner.nextBoolean();
  28.        System.out.println("It is " + isCertified +
  29.          " that " + name + ", age "
  30.          + age + ", is certified.");
  31.      }
  32.  
  33.      public static void main(String[] args) {
  34.        if (args.length != 1) {
  35.          System.err.println("usage: java TextScanner2"
  36.            + "file location");
  37.          System.exit(0);
  38.        }
  39.        readFile(args[0]);
  40.      }
  41.    }


DataScanner의 바깥쪽 Scanner 오브젝트는 한번에 한 라인씩 파일을 읽는다. readFile() 메소드는 각 라인을 두번째 스캐너에 전달하고, 이 두번째 스캐너는 콤마로 ,구획된 데이터를 파싱하고 콤마 양쪽의 흰 여백을 삭제한다. 다음 token이 특정타입의 token인지 아닌지 분석하여 다음 token을 그 타입의 인스턴스로 취급하도록 하는 hasNext()next()메소드들도 있다. 예를 들어 nextBoolean()은 다음 token을 boolean으로 취급하여 "true" 또는 "false" 스트링과 매치시킨다. 매칭이 이뤄지지 않으면 java.util.InputMismatchException이 던져진다. DataScannerparseLine() 메소드는 각 라인이 어떻게 String, int, boolean으로 파싱되는지 보여준다.

Compile DataScanner. Then run it as follows:

DataScanner를 컴파일하고 다음과 같이 구동시키자.

   java DataScanner Employee.data

다음과 같은 결과가 나타난다.

   It is true that Joe, age 38, is certified.
   It is true that Kay, age 27, is certified.
   It is false that Lou, age 33, is certified.

콤마를 구획문자로 사용하고 싶을 것이다. 다시 말해 다음과 같이 시도해보려고 할 것이다.

   lineScanner.useDelimiter(",");

이는 결국 InputMismatchException로 끝날 것이다. 이는 boolean으로 변경하려는 token에 여분의 공간이 포함되며 이 공간은 "true"나 "false" 에 매치되지 않기 때문이다. 일반적인 표현문의 모든 애플리케이션에 해당되는 케이스이므로 패턴을 구축하는 데 있어서 특히 세심한 주의가 필요할 것이다.

Scanner에 대한 좀 더 자세한 정보는 formal documentation를 참고하기 바란다.

저자 : Daniel H. Steinberg
원문 출처 : http://kr.sun.com/developers/techtips/c2004_1201.html

2007/06/24 11:28 2007/06/24 11:28
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다