갓-자바 랭귀지로 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
확실히 자바로 문제 풀면 이런거 잘 알아두는게 좋은듯 ㅋㅋ 코드포스에서 Petr씨 코드 눌러보면서 입출력 어케하는지 보고 써먹으니 좋았음
궁금한 게 PS할 때 자바 쓰면 좋은 점이 뭐임? 익숙해서 그런건가
Petr 거네 ㅋㅋㅋㅋ