Java로 알고리즘 문제를 푸는 중 Scanner 클래스를 사용했을 때 시간초과가 날 경우 BufferedReader을 사용하여 해결했다.
그런데 Scanner와 BufferedReader에 관해 의문점이 들기 시작했다.
이러한 생각을 기억보다는 기록으로 남기고 싶기에 Scanner와 BufferedReader의 역할과 차이점을 알아보려 한다.
Scanner 클래스는 입력받은 데이터(바이트)를 다양한 타입으로 변환하여 반환하는 클래스이다. 간단하게 기본형과 String 타입을 정규표현식을 사용해 파싱(parse)할 수 있다.
데이터를 한번에 읽어와 버퍼에 보관한 후 버퍼에서 데이터를 읽어오는 방식으로 동작하는 클래스이다. 즉 사용자가 입력한 문자 스트림을 읽는 것(read) 라고 한다.
그럼 먼저 간단하게 Scanner를 작성법을 알아보자.
import java.util.Scanner;
...
Scanner sc = new Scanner(System.in);
String st = sc.nextLine();
위와 같이 System.in을 통해 Scanner 객체를 생성한다.
System.in이란?
사용자로부터 입력을 받기 위한 입력 스트림이다. Scanner 클래스뿐 아니라 다른 입력 클래스들도 System.in을 통해 사용자 입력을 받아야 한다.
다음으로 BufferedReader 작성법도 알아보자.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
...
public static void main(String[] args) throws IOException {
BufferReader br = new BufferedReader(InputStreamReader(System.in));
String st = br.readLine();
int a = Integer.parseInt(br.readLine());
int b = Integer.parseInt(st);
}
위와 같이 BufferedReader는 매개변수로 InputStreamReader를 사용하여 객체를 생성한다.
InputStreamReader 란?
문자 기반의 보조 스트림으로써 바이트 기반 스트림을 문자 기반 스트림으로 연결시켜 주는 역할을 한다.
또한 패키지의 경우 java.io 하위의 BufferedReader, InputStreamReader 와 예외처리를 위한 IOException 를 사용하며 모두 간단히 java.io.* 로 모두 포함할 수 있다.
본인은 Scanner는 Parse로써 사용자가 입력한 텍스트를 token 단위로 잘라 특정한 형태로 반환하는 것이며 BufferedReader는 Read로 사용자가 입력한 데이터 자체를 그대로 읽어들이는 것이라고 이해했다.
백준에서 작성된 입력속도 비교표를 확인해보면 BufferedReader는 0.68585초, Scanner는 4.8448초가 소요되며 둘의 시간차이가 생각보다 크다.
왜 빠를까? 여기선 CPU를 고려해야 하는데 문자가 입력될 때마다 CPU가 하나하나 입출력을 하는 것보다 버퍼에 어느정도 쌓아두고 가득차거나 개행이 일어날 때마다 입출력을 처리하는 것이 훨씬 효율적이다.
그럼 Scanner와 BufferedReader의 예제를 작성해보고 실행속도 차이를 몸소 느껴보자.
간단한 A와 B 두 수의 합을 구하는 백준 1000번 문제를 풀어보았다.
먼저 Scanner를 사용해서 풀어보았다.
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
int sum = a + b;
System.out.println(sum);
sc.close();
}
}
Scanner 객체를 생성해서 nextInt()함수를 통해 a와 b를 입력받아 합을 구했다.
그리고 BufferedReader로도 풀어보았다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
StringTokenizer st = new StringTokenizer(s);
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
System.out.println(a+b);
br.close();
}
}
BufferedReader를 객체를 생성하고 한줄 단위로 처리할 수 있는 readLine() 함수를 통해 s에 저장한 뒤 StringTokenizer 객체를 사용하여 a와 b에 공백을 기준으로 나누어 저장한뒤 합을 구했다.
StringTokenizer 란?
문자열을 지정한 구분자로 문자열을 쪼개주는 클래스이다.
그렇게 쪼개어진 문자열을 우리는 토큰(token)이라고 부른다.
java.util.StringTokenizer 라이브러리를 import해야 사용할 수 있다.
Scanner를 사용했을 경우 212ms, BufferedReader는 124ms로 BufferedReader의 코드길이가 더 김에도 빠른 속도를 보여주었다.
다만 메모리의 경우 Scanner가 17704KB, BufferedReader가 14188KB로 큰 차이는 나지 않았다. 이를 통해 적은 입력값에는 Scanner를 사용해도 무관할 것으로 생각이 들었다.
상황에 따라 Scanner와 BufferedReader를 적절히 사용해야 겠지만 속도 차이가 영향을 주게되는 경우엔 BufferedReader를 사용하여 코드를 작성해야겠다.
원래 입출력과 관련해서 BufferedReader와 세트 메뉴처럼 다루는 BufferedWriter와 Sout에 대해서도 함께 다루고 싶었다.
예를 들어 BufferedReader/BufferedWriter VS Scanner/Sout 별로 비교도 따로 해보고 싶었지만 포스팅이 길어질 것 같아 추후에 다뤄보려 한다.
int값 받는 상황에서 scanner가 더빠를때도 있나요? int.parseint(br.readline)로 72ms였는데 sc.nextint로 68ms로 해결한분 있네요