Develop/Algorithm

Java BufferedReader, BufferedWriter 사용법

IJY 2022. 7. 26. 09:05

Java를 배우기 시작하여 입출력을 구현할 때 사용하는 메서드는 일반적으로 출력에는 System.out.println() 메서드를 사용하고 입력에는 Scanner 클래스를 사용하여 구현하게 될 텐데, 알고리즘 문제를 풀다 보면 속도로 인한 문제가 발생하게 됩니다.

이는 System.out.println() 메서드와 Scanner 클래스의 메서드 동작에 걸리는 시간으로 인해서 발생하는 경우도 있기 때문에 이를 해결하고자 입출력 속도가 빠른 BufferedReader와 BufferedWriter 클래스를 찾아서 사용하게 됩니다.

이 때 해당 클래스들의 사용법을 자주 사용을 하다 보면 외워지겠지만.. 아직은 사용법을 찾아서 사용하다보니 사용법을 정리한 포스팅을 작성하게 되었습니다.

 

Buffered?

클래스의 이름을 보면 유추할 수 있듯이 데이터를 입력받을 때 버퍼를 사용하여 입력을 받는 기능을 구현한 클래스입니다.

왜 바로 입력을 받아서 처리하는 것보다 중간에 버퍼를 사용하는 게 더 빠르지?라는 의문이 들 수 있습니다.

간단하게 설명을 한다면, 입출력 장치의 속도와 시스템 처리 속도의 차이가 매우 크기 때문에 이 싱크를 맞춰주는데 들어가는데 시간이 소요되게 되는데 하나의 데이터 입출력마다 이 처리를 하는 것보다 버퍼에 데이터를 넣어둔 후 한 번에 요청하는 게 더 빠르기 때문이라고 볼 수 있습니다. (정말 간단한 예를 들자면, 짐을 옮기는데 1개씩 왔다 갔다 옮기는 방식과 수레로 한 번에 여러 개를 옮기는 방식이라고 이해하셔도 될 것 같습니다.)

그럼 이제 각각 클래스의 사용법을 알아보도록 하겠습니다.

 

BufferedReader

BufferedReader 클래스를 사용하려면 일단 "java.io.BufferedReader", "java.io.InputStreamReader", "java.io.IOException" 패키지의 import가 필요합니다.

각각 BufferedReader 클래스, BufferedReader 생성자 매개변수로 넣어줄 InputStreamReader, BufferedReader의 readLine() 메서드 사용 시 발생할 수 있는 "IOExcetpion" 에러 처리를 위한 패키지라고 볼 수 있습니다.

사용법을 알아보기 위해 간단하게 문자열 1줄을 입력받아서 출력하는 예제 코드를 보도록 하겠습니다.

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

public class Test {
	public static void main(String[] args) Throws IOException {
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        String inputStr = br.readLine();
        
        System.out.println(inputStr);
    }
}

코드를 보면 BufferedReader 객체를 생성해서 사용하는데 InputStreamReader 객체를 생성하여 매개변수로 넣어주고, InputStreamReader 객체를 생성할 땐 System.in을 매개변수로 넣어줍니다. 그리고 해당 인스턴스의 참조 변수인 br을 사용하여 readLine() 메서드를 호출하여 문자열 1줄을 입력받아 inputStr 변수에 할당 후 System.out.println() 메서드로 출력하여 확인합니다.

Scanner의 경우 공백을 기준으로 입력을 받지만 BufferedReader의 경우 메서드명에서 유추 가능하듯이 라인 단위로 입력을 받게 되며 반환을 String 타입으로 하게 됩니다. 따라서 BufferedReader를 사용해 데이터를 입력받은 후 공백 단위로 데이터를 처리하거나 다른 타입으로의 처리가 필요하다면 추가적인 작업이 필요하게 됩니다.

일반적으로 공백 단위 데이터 처리의 경우 StringTokenizer 클래스를 사용하고, int 타입으로의 형 변환은 Interger.parseInt() 메서드를 사용합니다.

문자열을 공백 단위로 파싱 하는 방법으로 String의 split() 메서드가 존재한다. StringTokenizer와의 성능 차이가 궁금하여 알아보던 중 StringTokenizer가 legacy 코드이며, 호환성 유지 명목으로 남아있는 클래스라는 것을 알게 되었습니다. (링크)

StringTokenizer 문서 캡처 이미지

성능의 경우 특정 상황인 경우 StringTokenizer 클래스 사용이 빠를 수 있지만, 내부 구현 로직이 비효율적이라 특정 상황이 아닌 경우에 동작 속도가 현저히 느려질 수 있기에 일정한 속도로 동작하는 split() 메서드 사용을 지향하는 것이 올바르다고 생각합니다. (성능에 대한 정리를 잘 해 놓은 블로그 : 링크)

 

BufferedWriter

BufferedWriter 클래스를 사용하려면  "java.io.BufferedWriter", "java.OutputStreamWriter", "java.io.IOException" 패키지의 import가 필요합니다.

각각 BufferedWriter 클래스, BufferedWriter 생성자 매개변수로 넣어줄 OutputStreamWriter, BufferedWriter의 write(), flush(), close() 메서드 사용 시 발생할 수 있는 "IOExcetpion" 에러 처리를 위한 패키지라고 볼 수 있습니다.

사용법을 알아보기 위한 예제 코드를 보기 전 대략적인 동작에 대하여 설명 후 코드를 보도록 하겠습니다.

BufferedWriter의 경우 "write()" 메서드를 통해서 버퍼에 문자열을 저장한 후 "flush()" 메서드를 사용하여 버퍼에 저장된 문자열을 출력합니다. BufferedWriter를 다 사용을 했다면 "close()" 메서드를 통해서 stream을 닫아주는 게 좋습니다. 그렇지 않다면 이후 해당 메모리 반환을 전적으로 GC에게 의존하게 됩니다. 그리고 추가적으로 "close()" 메서드 호출 시 stream을 닫기 전, 버퍼에서 저장되어 있는 문자열을 flush 후 stream을 닫게 된다는 점을 인지하고 계시면 좋습니다.

이제 예제 코드를 보면서 사용법을 알아보도록 하겠습니다. 해당 코드는 0부터 9까지의 숫자를 라인 단위로 출력하는 코드입니다.

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        for(int i = 0; i < 10; i++)
        	bw.write(Integer.toString(i) + "\n");	// String.valueOf(i)로 변경해도 무방
        bw.flush();
        bw.close();	// flush 후 stream close 동작, 확인을 위하여 flush() 주석 후 테스트 해보기
    }
}

BufferedReader와 비슷한 방법으로, BufferedWriter 객체를 생성할 때 OutputStreamWriter 객체를 생성하여 매개변수로 넣어주고, OutputStreamWriter 객체를 생성할 땐 System.out을 매개변수로 넣어줍니다. 그리고 해당 인스턴스의 참조 변수인 bw를 사용하여 write() 메서드를 호출하여 출력할 값들을 버퍼에 저장합니다. 이때, BufferedWriter의 경우 System.out.println() 메서드처럼 자동으로 다음 라인으로 넘어가지 않기 때문에 다음 라인으로 넘기고 싶다면 "\n"을 추가로 넣어주셔야 합니다. 이후 출력 데이터를 전부 버퍼에 넣었다면 flush() 메서드를 사용하여 화면에 출력시킵니다. 이후 더 이상 사용하지 않는다면 close() 메서드를 호출하여 stream을 닫고 종료합니다.

버퍼에 출력 데이터를 저장하는 write() 메서드의 경우 아래 이미지처럼 매개변수로 "단일 character, char[], String"을 받기 때문에 숫자나 다른 타입이라면 String으로 형 변환이 필요합니다.

BufferedWriter의 write 메서드 캡처 이미지


해당 포스트는 BufferedReader와 BufferedWriter 클래스의 사용법에 대해서 간단하게 정리하려고 했던 글이었기에 동작에 관련된 자세한 내용은 포함되어 있지 않습니다. 대강 이런 식으로 동작하고 이렇게 사용을 한다는 것만봐주시면 될 것 같습니다. 혹시나 틀린 내용이 있을 시 댓글로 알려주시면 수정하도록 하겠습니다.