Java Scanner VS BufferedReader 비교하기

이지수·2023년 8월 20일

Java

목록 보기
1/1

Java로 코딩테스트를 준비해본 사람이라면(특히, 백준처럼 Input을 받는 부분을 반드시 구현해야하는 경우) 누구나 Scanner 클래스를 사용해보았을 것이다.
그리고 코딩테스트 중 실행 속도에 조금 관심을 가졌다면, BufferedReaderScanner보다 더 효율적이라는 이야기를 들어보았을 것이다.

결론부터 말하자면 BufferedReader를 사용했을 때 input 시간이 훨씬 단축되는 것은 맞다. 그래서 보통 input 값이 클수록 BufferedReader를 사용하는 것을 권장하고 있다.

그렇다면 이 시점에서 한 가지 궁금증이 생긴다.
BufferedReaderScanner보다 빠른 걸까?
둘의 차이점이 무엇이고 장단점은 어떤 것이 있을까?

요 궁금증을 해소하기 위해 요모조모 찾아본 내용을 아래에 간략하게 정리해보자.


Scanner와 BufferedReader란?

두 클래스는 모두 입력 장치에서 문자열을 입력 받을 수 있는 기능을 제공한다.

문자열을 입력 받을 때, Scanner와 BufferedReader 모두 버퍼를 이용한다.
버퍼는 입력장치에서 전달된 문자를 RAM에 임시저장하는 공간인데, 아래의 예시에서 프로그램은 12번의 I/O 작업을 거치지 않고 버퍼에서 한번에 데이터를 가져올 수 있다.
buffer 사용 예시

구체적으로 각 클래스를 사용하는 방법은 아래와 같다
예를 들면 콘솔에서 입력을 받아서 출력하고 싶다고 해보자

Scanner를 사용하는 방법

import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);	//Scanner 사용

        String input = sc.nextLine();
        
        System.out.println(input)
    }
}

BufferedReader를 사용하는 방법

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Test {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));	//BufferedReader 사용

		String input = br.readLine();

		System.out.println(input);
	}
}

위 두 코드 모두 동작 방식은 동일하다.
nextLinereadLine 모두 입력 받은 값의 한 줄 전체를 읽어오게 된다.
그래서 실제로 실행해보면 변수 input은 입력값이 들어올때까지 기다렸다가 입력이 들어오면 그 뒤 코드를 수행한다.


Scanner와 BufferedReader로 file 읽기

ScannerBufferedReader는 사용자 입력 뿐 아니라 파일도 읽을 수 있다.
위 코드와 거의 차이는 없고, System.in 대신 FileReader 클래스를 사용하면 된다

Scanner sc = new Scanner(new FileReader("file.txt");
BufferedReader br = new BufferedReader(new FileReader("file.txt");

두 클래스 모두 내부적으로는 버퍼를 사용하고 있다.
사용자가 콘솔에 어떤 문자를 입력하면 이를 버퍼에 모아두고, 프로그램에서 한번에 읽어들이는데 이는 기본적으로 I/O 작업 횟수를 최소화 하기 위해서다.






차이점 비교하기

위 예시 코드를 비교했을 때 결과는 똑같지만 코드는 약간 다르다.
인스턴스를 생성할 때 BufferedReaderInputStreamReader를 인자로 받고 있고, IOException 예외 처리도 해줘야한다.

그렇다면 우선 두 클래스의 차이가 정확히 무엇일까?



Scanner VS BufferedReader

  • Scanner는 1KB char 버퍼 사이즈를 가지고, BufferedReader는 8KB로 훨씬 큰 버퍼를 가진다.
  • BufferedReader는 동기적(Synchronous)이지만 Scanner는 그렇지 않다. 만약 멀티스레드에서 입력 작업을 한다면 동기적인 BufferedReader를 사용하는 것이 좋다.
    (Synchronous란? 여러 스레드가 하나의 공유 자원을 사용할 때 동시에 접근하지 못하도록 막는 것을 말한다. 자세한 내용은 블로그를 참고하세요)
  • BufferedReader는 Scanner보다 속도가 빠르다. Scanner는 input 데이터를 파싱하지만 BufferedReader는 단순히 일련의 문자를 읽어들이기 때문이다.

위 3가지 차이점 중 가장 주목할 부분은 3번째 내용이다.
Scanner의 내부 라이브러리를 확인해보면 int, boolean, float, double 등으로 파싱해주는 내장 함수들이 존재한다.


예를 들어, Scanner.nextInt()를 호출하면 아래와 같은 절차를 거친다
1. 같은 기수(10진, 2진 등)의 캐시가 존재하는지 확인
2. 입력 받은 대로 기수 설정
3. String으로 버퍼에서 문자열을 전달 받음
4. '\s', '\n' 등 구분기호(delimiter)가 존재하는지 확인
5. string을 int로 파싱

즉, 단순히 버퍼에서 String을 입력 받아오는 BufferedReader와 달리 Scanner는 내부적으로 데이터를 파싱해 가져오기 때문에 해당 로직만큼의 시간이 더 소요된다.
게다가 Scanner는 BufferedReader보다 버퍼가 훨씬 작기 때문에 입력받는 데이터가 많을수록 I/O 작업량도 늘어나기 때문에 입력량이 많을수록 차이는 더욱 커지게 된다.




코드 상의 차이점

자, 그러면 Scanner와 BufferedReader의 코드에 차이가 있는 이유는 무엇일까?
당연히 Scanner와 BufferedReader의 내부 라이브러리 코드 구현이 다르게 되어있기 때문이지만
정확한 이유를 알면 좋으니 빠르게 확인해보자


1. InputStreamReader의 유무?

클래스의 인스턴스를 생성할 때 인자로 던지는 객체에 차이점이 하나 보인다

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

둘다 System.in 객체를 전달해주고 있지만 BufferedReader만 InputStreamReader를 인자로 주는 이유는 단순하다
Scanner는 System.in 객체를 전달하면 내부적으로 InputStreamReader를 알아서 생성해 사용하지만
BufferedReader는 우리가 별도로 만들어주어야 한다.
아래는 Scanner 내부 함수 코드이다

public Scanner(InputStream source) {
     this(new InputStreamReader(source), WHITESPACE_PATTERN);
}

참 단순한 이유다
(나만 궁금했을지도..)


2. IOException 예외 처리 유무

BufferedReader는 Scanner와 달리 IOException 예외 처리를 해주는 것이 필수다.
그래서 throws를 사용하거나 try catch 문을 통해 예외처리를 해주는 것이 일반적이다.

BufferedReader의 nextLine() 함수를 들여다보면 해당 함수에도 throws 를 통해 IOException 예외 처리해주고 있다.

public String readLine() throws IOException {
        return readLine(false, null);
    }

throws 키워드는 소위 '예외 처리 떠넘기기'라고도 얘기하는데, 해당 함수에서 예외처리 하지 않고 해당 함수를 호출한 곳에서 예외처리를 하도록 유도하는 것이다

Scanner는 각 함수마다 try catch로 예외를 처리하고 있기 때문에 Scanner를 사용할 때는 별도의 예외처리가 강요되지는 않는다.

public int nextInt(int radix) {
        ...
        try {
            ...
        } catch (NumberFormatException nfe) {
            ...
        }
    }

(throws, Chained Exception에 대한 자세한 정보는 블로그를 참고하세요)






Scanner, BufferedReader의 장단점

길고 긴 포스팅의 끝으로 장단점에 대해 알아보자!

Scanner

장점

  • 내부적으로 예외처리 및 파싱을 처리해주기 때문에 적은 양의 코드로 간편하게 사용할 수 있다
  • nextInt, nextBoolean 등 원하는 데이터 형식으로 바로 입력을 받을 수 있다
  • 작성해야 하는 코드 라인이 적기 때문에 가독성이 좋다

단점

  • Synchronous(동기)를 지원하지 않기 때문에 멀티 스레드를 사용할 경우에는 부적절하다
  • 버퍼의 크기가 작고, 파싱 로직이 포함되어있기 때문에 큰 입력값을 받을 경우 시간이 오래 걸린다

따라서 Scanner는 적은 양의 데이터를 파싱해서 사용할 경우에 사용하는 것이 좋다
코딩테스트의 경우, 인풋이 1~3줄로 끝나는 경우에 Scanner를 사용하면 코드를 작성하는 시간도 단축하고 가독성도 높일 수 있다.



BufferedReader

장점

  • Synchronous(동기)를 지원하기 때문에 멀티 스레드를 사용할 경우 공용 자원 관리에 용이하다
    (단, 스레드의 수가 많을 경우 block이 걸리기 때문에 Scanner보다 처리가 느려질 수도 있다)
  • 큰 입력값, 혹은 파일을 읽을 때 Scanner에 비해 빠르게 읽어올 수 있다

단점

  • 예외처리, 파싱 등의 작업을 개발자가 별도로 해줘야하기 때문에 필수로 작성해야하는 코드 라인이 많다
  • 사용자가 버퍼를 직접 관리해야한다(close 처리 등이 자동으로 되지 않는다)

BufferedReader는 큰 입력값을 빠르게 받아야 할 경우에 사용하는 것이 좋다.
코딩테스트의 경우, 인풋이 몇백줄씩 되는 극단적인 경우가 예상될 때 시간 단축 목적으로 사용하기 좋다.
난이도가 높은 문제는 BufferedReader 사용 유무가 시간 이슈를 통과하느냐를 결정할 정도로 중요한 역할을 한다.
대신 예외처리나 버퍼 관리, 파싱을 직접 해야하기 때문에 코드를 빠르게 작성할 수 있어야 한다.
(문제를 많이 풀면 손가락이 자동으로 반복 코드를 작성할 수 있게 된다..!)




참고 자료

Difference Between Scanner and BufferedReader Class in Java
StackOverFlow - Scanner vs. BufferedReader
[Java core] BufferedReader vs Scanner

0개의 댓글