코딩테스트를 준비하시는 분들이라면 Scanner와 BufferedReader의 차이점에 대해서 궁금해하시는 분들이 계실 거라고 생각합니다.
오늘은 이 둘의 차이점과 속도차이가 왜 생겨나는지에 대해서 알아보겠습니다.
본론부터 말하자면 속도측면에서 더 우세한건 BufferedReader입니다.
입력 데이터가 그렇게 크지 않으면 그림과 같은 차이점을 보이겠지만, 입력 데이터가 커지면 커질수록 그 차이는 더 심해지게 됩니다.
더 자세하게 두 방식의 동작 원리를 통해 좀 더 살펴보겠습니다.
Scanner
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int number = sc.nextInt();
System.out.println("The number is: " + number);
}
}
Scanner 클래스는 기본적으로 입력 데이터를 토큰화(Tokenization)하여 다양한 데이터 타입으로 변환할 수 있도록 설계되었습니다.
일단 사용자로부터 데이터를 입력받고 내부적으로 데이터를 버퍼링하여 읽습니다.
여기서 버퍼링이란 데이터를 한 번에 일정 크기 단위로 메모리에 저장해놓고, 그 데이터를 처리하면서 읽어가는 것입니다.
후에 읽은 데이터를 기본적으로 공백을 기준으로 데이터를 나누고, 토큰화된 데이터를 원하는 타입(int, double, string)으로 변환한 후 정규 표현식을 사용하여 구분자를 커스터마이징 하게 됩니다.
해당 동작 과정에서 정규 표현식 기반의 추가 파싱 작업으로 인해서 BufferedReader에 비해 속도가 느려지게 됩니다.
좀 더 이해하기 쉽게 예시로 설명해 드리면 다음과 같습니다.
도서관에서 책을 빌린다고 가정해보겠습니다.
1. 먼저 책을 한 권 빌립니다.
2. 그 책을 다 읽고 다시 도서관에 가서 또 다른 책을 빌리는 과정을 반복합니다.
즉, 도서관에 들어가서 하나씩 처리하고 다시 나오는 방식입니다.
BufferedReader
public class Main {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int number = Integer.parseInt(br.readLine());
System.out.println("The number is: " + number);
}
}
BufferedReader 클래스는 대량의 데이터를 효율적으로 읽어오기 위해 설계되었습니다.
일단 InputStreamReader나 FileReader를 통해 바이트 데이터를 문자 데이터로 변환합니다.
그리고 지정된 크기의 버퍼를 사용해 입력 데이터를 한 번에 읽어 들입니다.
읽은 데이터를 라인 단위 또는 캐릭터 단위로 처리하게 됩니다.
결론적으로 입력받은 데이터를 토큰화하지 않고, 단순히 문자열로 반환하는 방식입니다.
이번에도 좀 더 이해하기 쉽게 예시로 설명해 드리면 다음과 같습니다.
1. 도서관에 들어가서 여러 권의 책을 빌립니다.
2. 한 번에 빌려갈 수 있도록 준비된 많은 책을 한꺼번에 대출 신청을 기록하고 빠져나옵니다.
즉, 여러 권의 책을 한 번에 빌린다는 개념으로 이해하시면 됩니다.
속도 차이 발생 이유
동작 원리로는 그래서 속도 차이가 왜 생기는지 이해가 안 되실 수도 있어, 이유와 함께 설명드리겠습니다.
1. 버퍼 크기와 입출력 처리 방식
Scanner는 상대적으로 작은 버퍼(InputStream의 표준 버퍼 크기를 사용)를 사용합니다.
데이터를 한 글자씩 읽고, 정규 표현식으로 추가 처리를 수행합니다.
BufferedReader는 사용자 정의 버퍼 크기를 지원합니다. 기본적으로 8KB입니다.
데이터를 한 번에 버퍼로 읽은 뒤 반환합니다.
결론적으로, BufferedReader는 I/O 호출 횟수가 적어 Scanner에 비해 상대적으로 빠릅니다.
2. 정규 표현식과 토큰화 작업
Scanner는 데이터를 읽을 때 정규 표현식을 기반으로 구분자 매칭과 파싱 작업을 수행합니다.
이 과정에서 정규식 매칭은 추가적인 연산을 발생시키게 됩니다.
BufferedReader는 이와 다르게 단순히 문자열을 읽는 역할만 하므로 추가적인 파싱 작업을 필요로 하지 않습니다.
그렇기 때문에 추가적인 연산의 필요성에 의해, Scanner의 속도가 느려질 수 있습니다.
3. 메서드 호출 빈도와 라인 단위 처리
Scanner는 데이터를 토큰 단위로 읽기 위해 여러 번의 메서드 호출이 필요합니다. (가령, nextInt(), hasNext()...)
BufferedReader는 데이터를 라인 단위로 읽기 때문에, 동일한 데이터를 처리할 때 호출 빈도가 더 적습니다.
위와 같은 이유들로 인해서 입력 데이터의 크기에 따라서 Scanner와 BufferedReader의 속도 차이가 발생하게 됩니다.
실제 속도 비교
약 천만 개의 정수 데이터를 기반으로 각각의 방식에 대해서 속도를 측정해 보면 아래와 같습니다.
Scanner & BufferedReader 사용 사례
그럼 상대적으로 느린 Scanner는 무조건적으로 사용하면 손해일까요? 그건 아닙니다.
물론 속도가 중요한 코딩테스트에 있어서는 BufferedReader가 당연히 빠르겠지만,
서로 다른 상황에선 융통성 있게 방식을 골라야 합니다.
Scanner 사용 사례
1. 입력 데이터를 다양한 데이터 타입으로 즉시 변환해야 할 때
2. 정규 표현식이나 커스텀 구분자가 필요한 경우
BufferedReader 사용 사례
1. 대량의 데이터를 효율적으로 읽어야 할 때
2. 파일이나 네트워크 스트림에서 입력을 처리할 때
3. 데이터 파싱 작업을 별도로 하고자 할 때
상세하게 Scanner와 BufferedReader에 대해서 알아봤습니다.
물론 여태껏 "빠르니까 당연히 사용해야지"라는 생각으로 BufferedReader를 주로 사용해 왔지만,
이 글을 통해 각 상황에 맞게 융통성 있게 사용할 줄 아는 개발자로 성장해야겠습니다.
'Daily' 카테고리의 다른 글
[CDC 프로젝트] Oracle DB to MySQL(1) (1) | 2025.01.04 |
---|---|
CDC(Change Data Capture)란? (0) | 2024.12.09 |
모놀리식 프로젝트 DDD 패턴으로 전환하기 (1) | 2024.11.10 |
무중단 배포란? (0) | 2024.10.21 |
[Spring Boot] 동시성 제어 (3) | 2024.10.12 |