viewimage.php?id=3dae&no=24b0d769e1d32ca73cef8ffa11d02831424b310c0eeaa8669b91e5ace0a74580b6ca6be0564f57166e1e388f55945535e70982dc710ec1307f


갓-자바 랭귀지로 PS 입문해보실려는...분들 위해서 함 번역해보았읍니다.


저도 좆밥이라 공부해볼겸.... ^-^


원문 : https://www.cpe.ku.ac.th/~jim/java-io.html


(PC에서 보시면 잘보임니다..)


=================================================================


Faster Input for Java



ACM ICPC에서 Java 언어로 대회 참가가 가능하지만, Java가 현실적인 선택일까요?


같은 문제를 푼다고 하였을 때, 자바는 C보다 더 많은 양의 코드를 타이핑 해야합니다.


그리고 (가장 중요한 점이지만) 자바는 일반적으로 C보다 느립니다. 입/출력은 (C와 비교했을 때) 가장 느린 부분 중 하나죠.


그럼, 자바에서 입/출력 속도를 빠르게 만드는 게 가능할까요?




문제: 스캐너는 느.....리.....다


Scanner 클래스를 이용하면 쉽게 입력값을 파싱할 수 있지만, 너무 느립니다.


BufferedReader 와 StringTokenizer 를 사용하는 편이 훨씬 빠르죠. 그렇지만, 대회에서 많은 타이핑이 필요로 합니다.


Java를 좀 더 쉽게 써먹어볼 순 없을까요?




많은 타이핑을 해야함:


C

double x;

scanf("%lf", &x);

Java with Scanner

Scanner input = new Scanner(System.in);

double x = input.nextDouble();

Java with

BufferedReader

import java.io.*;

// throw IOException 예외처리는 필수입니다. ^^


BufferedReader br = new BufferedReader ( new InputStreamReader( System.in ) );


// 이 코드는 한 라인에 입력값이 딱 1개만 주어져 있을때만 작동합니다.

double x = Double.parseDouble( br.readLine() );




얼마나 느릴까?


필자는 한 파일에서 10,000,000개의 int 와 double을 읽는 테스트를 진행해보았습니다. 걸린 시간은 아래 표 에 나와있습니다.


본 테스트는 인텔 코어2듀오 2.4Ghz CPU, WinXP Pro XP3, Sun JDK 6.0r22 와 GNU gcc 4.5.2 환경에서 진행하였습니다.



표1. 10,000,000 개의 int 값을 파일에서 읽을 때



입력 방법

시간 (초)

scanf("%d", &arg);

3.78

Scanner.parseInt();

29.52

BufferedReader + inline Integer.parseInt

2.89

BufferedReader + Reader.nextInt method

3.01




표2. 10,000,000 개의 double 값을 파일에서 읽을 때


입력 방법

시간 (초)

scanf("%lf", &arg);

11.9

Scanner.parseDouble();

66.86

BufferedReader + inline Double.parseDouble

3.06

BufferedReader + Reader.nextDouble method

3.14





확실히 Scanner 클래스를 이용하는 방법이 C 보다 느리다는 것을 표에서 보여주네요.


필자가 Aj Jittat's ACM Training Session에서 몇가지 예제 문제를 Java로 작성해보았는데, 결과는 런타임 초과로 인한 Failed 였습니다.




BufferedReader 에 파싱 메소드를 끼얹는 방법은, 거의 C에 근접할정도로 빠른 결과를 보여주고 있습니다.


그렇지만, 많은 타이핑이 필요하죠. 이클립스 같은 IDE를 사용하신다면,


자동으로 import와 try/catch 및 throw 구문을 입력해주기 때문에 어느 정도 도움이 될 수는 있겠으나, 충분하진 않습니다.




리스트 1. 아래는 Scanner와 BufferedReader를 사용해 값을 읽어들이는, 예제 메소드 입니다.


한 줄에 여러 개의 입력값이 들어가는 경우, 스트링 토큰(String token)으로 각 단일 값으로 잘라(split)낼 수 있습니다.


입력값을 잘라내는 데에는, string.split() 보다 StringTokenizer 를 쓰는게 4배 이상 빠릅니다.



// Scanner를 이용해 정수 개수 세기

static int scanInteger(int count) {

 Scanner sc = new sc(input);

 int last = 0;

 while (count-- > 0) {

  last = sc.nextInt();

 }

 return last;

}


// BufferedReader를 이용해 정수 개수 세기

static int readIntegers(int count) throws IOException {

 BufferedReader rd = new BufferedReader ( new InputStreamReader(input) );

 StringTokenizer tokenizer = new StringTokenizer("");

 int last = 0;

 while (count-- > 0) {

  if (!tokenizer.hasMoreTokens() ) {

   tokenizer = new StringTokenizer(reader.readLine());

  }

  last = Integer.parseInt(tokenizer.nextToken());

 }

 return last;

}




















재사용가능한 코드 만들기


자바에서 입력값을 읽는 부담을 어떻게 줄여볼까요? 자바는 C/C++과 비교했을 때 어느정도 장점이 있긴 있습니다:


문법 오류가 발생할 경우가 적고... 아마도 생각의 실수를 조금 줄일 수 있고, 코드의 완결성이 좋고, 자동 컴파일 그리고 이클립스의 디버거.



자바 코드의 가독성을 높이기 위해, Reader 코드를 별도의 클래스에 집어 넣어봅시다.


( public 클래스일 경우, 하나의 소스 파일에 여러 개의 클래스를 넣을 수 있습니다. )


우리는 직접 만든 Reader 클래스를, 모든 ACM 코드에 복사하는 방법을 통해 결과적으로 오직 한번만 코드를 타이핑을 하였습니다.


문제를 읽고, 솔루션을 설계할 동안 팀원 중 한명은 코드를 타이핑을 하면 되는 거죠.



리스트 2 는 예시입니다. 혹시 더 짧고 효율적인 코드를 아신다면 알려주시기 바랍니다.


저는 string.split() 대신에 좀 더 빠른 StringTokenizer를 사용 하였습니다.


또한, Reader 객체는 생성하거나 관리할 필요가 없기 때문에, [인스턴스 생성 없이 즉시 사용할 수 있도록]


static 메소드를 사용하였습니다. 그리고, 패키지 내 기본 접근자를 사용할 것이기 때문에, public 을 타이핑 할 필요는 없습니다.



필자가 진행한 벤치마크에선, 입력 메소드를 개별 클래스에 넣는다고 해서 속도에 영향을 주거나 하지는 않았습니다.


하지만, 이렇게 하면 코드의 재사용성이 높아지고, 나머지 코드들도 보기가 쉬워집니다.



여전히 많은 코드가 필요하지만, 한 번만 타이핑하고 각 작업 클래스에 복사해서 쓸 수 있습니다.


즉, ACM 문제에서 아래처럼 사용이 가능하겠죠:


Reader.init( System.in ); // Reader 객체에 InputStream을 연결합니다.

double x = Reader.nextDouble();

int n = Reader.nextInt();




리스트 2. 아래는 int와 double 값을 읽는 예시 코드 입니다.


// 버퍼에 담긴 int와 double 값을 읽는 클래스입니다.

class Reader {

static BufferedReader reader; static StringTokenizer tokenizer; // Reader에 InputStream을 넣고 초기화하기 위해, init 메소드를 호출합니다. static void init(InputStream input) { reader = new BufferedReader( new InputStreamReader(input) ); tokenizer = new StringTokenizer(""); } // 다음 값을 얻습니다. static String next() throws IOException { while ( ! tokenizer.hasMoreTokens() ) { //TODO add check for eof if necessary tokenizer = new StringTokenizer( reader.readLine() ); } return tokenizer.nextToken(); } static int nextInt() throws IOException { return Integer.parseInt( next() ); } static double nextDouble() throws IOException { return Double.parseDouble( next() ); } }





별첨


PS를 정말 잘하시는분의 Java 템플릿 코드라네요... https://pastebin.com/ZiNDNTkC