1. BufferedReader & BufferedWriter란?
간단히 말해서 BufferedReader와 BufferedWriter란 버퍼를 이용하여 데이터의 입출력을 다루는 함수입니다.
그렇다면 우선 버퍼란 무엇일까요?
전에 버퍼에 대해 정리해둔 글입니다. 필요하시다면 참고해보세요.
윗 글에서 마지막에 버퍼는 데이터를 옮기는 바구니에 비유했듯이, 입/출력에서의 버퍼란 입/출력이 발생할때마다 데이터를 하나하나 전송하지 않고 차례대로 모아두었다가 특정 경계값이 들어오면 한번에 데이터를 전송할 수 있도록 입/출력 데이터를 보관하는 임시 저장소를 뜻합니다. 이렇게 버퍼를 이용하여 입출력을 다루게 되면 속도가 향상되며 시간 부하가 적어지는 장점을 갖습니다.
즉, BufferedReader와 BufferedWriter란 버퍼를 이용해서 데이터를 읽고 쓰는 함수로, 속도면에서 다른 입출력 함수에 비해 월등하게 빠르다는 장점을 지녔습니다.
2. BufferedReader
2-1. BufferedReader vs Scanner
Scanner와 BufferedReader는 모두 문자열을 입력받는 클래스지만 각 클래스가 지향하는 바가 다르므로 각 클래스의 특징에 대해 이해하고 상황에 맞게 사용하는 것이 좋습니다.
우선 Scanner에 대해 간략하게 이야기하자면,
Scanner란 데이터 유형과 문자열을 구문 분석 할 수 있는 텍스트 스캐너로 ①스페이스(공란)과 엔터(줄바꿈)을 경계값으로 인식하며 ② 입력받는 즉시 자료형(데이터 타입)이 설정되므로 별도의 형변환이 필요없습니다.
그러므로 문자열에서 다양한 데이터 타입으로 구분이 필요한 경우나 문자열을 다양하게 쪼개야 하는 경우에는 Scanner를 사용하기에 적합합니다. 이 외에 좀 더 자세한 내용은 아래 Scanner에 대해 정리한 글을 참고하세요.
하지만, 이렇게 사용자에게 편리성을 제공해주는 것은 클래스 내부에 다양한 함수들이 존재하고 매 입력마다 다양한 경우의 수를 따져야 하므로 당연하게도 속도면에서 떨어질 수 밖에 없습니다. 특히 단순 입력의 양이 많아질수록 Scanner를 사용하는 것은 효율적인 면에서 뒤떨어지는 결과를 낳습니다.
단순 입력의 양이 많아질수록 빛을 발하는 입력 클래스는 BufferedReader입니다.
BufferedReader란 InputStreamReader에 버퍼링 기능이 추가된 클래스로 ①엔터(줄바꿈)만을 경계값으로 인식하며 ② 오직 String 타입만 다루며 ③ 매우 큰 버퍼 사이즈(8KB) 때문에 속도면에서 매우 빠르다는 장점을 가집니다.
즉, BufferedReader는 문자열을 단순히 읽고 저장하는 것이 중점이며 Scanner는 문자열을 구문하고 분석하는 것이 중점이 됩니다. 그러므로 입력 데이터를 가공없이 빠르게 저장할 때에는 BufferedReader를 사용하고 입력 데이터를 가공할 때에는 Scanner를 사용하는 것이 효율적입니다.
이 외에도 BufferedReader와 Scanner의 차이에는 동기화 여부, IOException 처리 등이 존재하지만, 두 클래스의 중점 위주로 정리를 해 보았습니다. 아래는 두 클래스의 차이점을 간략하게 표로 작성했습니다.
Scanner | BufferedReader | |
버퍼 사이즈 | 1KB | 8KB |
동작 | 문자열 구분 및 파싱 | 단순 읽고 저장 |
동기화 | X → 싱글 스레드 | O → 멀티 스레드 |
IOException | IOException 숨김 | IOException 던짐 |
2-2. BufferedReader 사용법
- BufferedReader를 사용하기 위해선 다음의 import가 추가적으로 필요합니다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
- 위에서 BufferedReader는 예외처리를 꼭 던져야 한다고 했습니다. 이때 방법은 두가지로 readLine()을 할때마다 매번 try & catch를 이용하여 예외처리를 해주거나 throws IOException을 이용하여 예외처리를 해주면 됩니다. 일반적으로는 후자의 방법을 많이 사용하므로 후자의 방법으로 예외처리를 해줍니다.
public static void main(String[] args) throws IOException{}
- 마지막으로 다음과 같은 방식으로 선언하고 readLine()을 통해 입력을 받아오면 됩니다.
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
- 전체적인 코드는 이와 같습니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
}
}
2-3. BufferedReader Method Summary
Modifier and Type | Method | Description |
void | close() | 스트림을 닫고 스트림에 저장되어있던 자료를 날립니다 |
Stream<String> | lines() | 스트림을 반환합니다. |
void | mark(int readAheadLimit) | 스트림의 현재 위치를 마크합니다. |
boolean | markSupported() | 현재 스트림에서 mark()의 가능 여부를 알려줍니다. |
int | read() | 한 글자만 읽습니다 |
int | read(char[] cbuf, int off, int len) | cbuf[]에서 off번째 글자부터 len 길이만큼 문자를 읽습니다. |
String | readLine() | 한 줄을 읽습니다. |
boolean | ready() | 스트림이 입력을 읽을 준비가 되었는지를 알려줍니다. |
void | reset() | 가장 최근 마크된 부분으로 스트림을 돌립니다. |
long | skip(long n) | n개의 문자를 건너뜁니다. |
+ close()에 경우 자바에서는 Garbage Collector/Collection에 의해서 내부 객체 및 리소스들이 정리되기 때문에 굳이 close()를 콜하지 않아도 이후에 자동으로 정리가 되어 문제가 발생하지 않습니다. 하지만 Garbage Collector가 실행되고 완료될때까지 스트림에 할당된 리소스들이 그대로 유지되기 때문에 최적화 관점에서 close()를 콜해 스트림을 종료해주는 것이 좋습니다.
+ read()에 경우 만약 숫자 8을 입력하면 8이 반환되는 것이 아니라, 8을 가르키는 아스키코드, 유니코드의 값이 나오는 것입니다.
+ reset()에 경우 만약 마크된 지점이 없을 때에는 가장 처음으로 돌아갑니다.
3. BufferedWriter
3-1. BufferedWriter vs System.out.println()
System.out.println()과 BufferedWriter은 모두 문자열을 출력하는 클래스지만 각 클래스가 지향하는 바가 다르므로 각 클래스의 특징에 대해 이해하고 상황에 맞게 사용하는 것이 좋습니다.
우선 System.out.println()에 대해서 간략하게 이야기를 해보자면,
System.out.println()은 Scanner와 마찬가지로 사용자에게 편리성을 제공해줍니다. 일단 println()에는 출력함수 print()와 줄바꿈함수 newLine()을 포함하고 있습니다. 또한 파라미터에 다양한 데이터 타입이 올 수 있도록 오버로딩 되어 있습니다.
즉, 단순히 파라미터만을 출력하는 것이 아니라 줄바꿈 기능이 포함되어 있으며 다양한 데이터 타입을 출력할 수 있도록 오버로딩 되어 있어 매우 편리하게 사용할 수 있습니다.
하지만 이러한 편리성은 속도면에서는 효율이 떨어질 수 밖에 없습니다. 위에 적은 이유 외에도 println()의 속도 저하 이유에는 다양한 이유가 존재합니다. 동기화, 인스턴스 등등 이후에 포스팅할 기회가 생긴다면 추가해놓겠습니다.
BufferedWriter은 println()과 같은 편의성은 존재하지 않지만 속도면에서는 뛰어납니다.
버퍼에 출력 문자열을 저장했다가 버퍼가 다 찼거나, 사용자의 요청이 들어오면 버퍼에 저장되어 있던 문자열을 한번에 출력하는 방식입니다.
즉, System.out.println()은 문자열을 구분하고 분석하는 것이 중점이 되고, BufferedWriter은 단순히 저장하여 출력하는것이 중점이 됩니다. 그러므로 데이터를 빠르게 출력하고자 할 때에는 BufferedWriter을 사용하고 다양한 데이터 타입을 출력하거나 다양한 문자열 구분 기능을 이용하고자 할 때에는 System.out.println()을 사용하는 것이 효율적입니다.
3-2. BufferedWriter사용법
- BufferedWriter를 사용하기 위해선 다음의 import가 추가적으로 필요합니다.
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.IOException;
- 위에서 BufferedWriter는 예외처리를 꼭 던져야 한다고 했습니다. 이때 방법은 두가지로 readLine()을 할때마다 매번 try & catch를 이용하여 예외처리를 해주거나 throws IOException을 이용하여 예외처리를 해주면 됩니다. 일반적으로는 후자의 방법을 많이 사용하므로 후자의 방법으로 예외처리를 해줍니다.
public static void main(String[] args) throws IOException{}
- 다음과 같은 방식으로 선언합니다.
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
- write()을 통해 버퍼에 출력문을 저장합니다 (⁎ write()으로 출력되는게 아니라 버퍼에 저장하는 것입니다)
bw.write("Hello World!");
+) BufferedWriter에는 println()과 달리 개행기능이 포함되어 있지 않기 때문에 줄바꿈을 하려거든 write("\n")을 하여 버퍼에 추가해주거나 newLine()을 콜해주시면 됩니다.
// write("\n") 예시
bw.write("Hello World! \n");
// newLine(); 예시
bw.write("Hello World!");
bw.newLine();
- flush()를 통해 버퍼안에 저장되어 있는 모든 문자열을 출력합니다. 만약 버퍼가 다 찬 경우엔 자동으로 flush()를 통해 모든 문자열이 출력됩니다.
bw. flush();
- BufferedWriter의 사용이 끝났으면 close()를 통해 스트림을 종료해줍니다. close()를 하기전에는 혹시라도 버퍼에 데이터가 남아있을지도 모르니 꼭 flush()를 통해서 스트림을 비워줍니다.
bw.close();
- 전체적인 코드는 이와 같습니다.
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("Hello World!");
bw.flush();
bw.close();
}
}
2-3. BufferedReader Method Summary
Modifier and Type | Method | Description |
void | close() | 스트림을 닫습니다. 닫기전에 flush()를 먼저 콜해줍니다. |
void | flush() | 스트림을 비웁니다. |
void | newLine() | 줄바꿈, 개행합니다. |
void | write(char[] cbuf, int off, int len) | cbuf[]의 off 위치부터 len 길이만큼 버퍼에 저장합니다. |
void | write(int c) | 한 글자 c의 코드값을 버퍼에 저장합니다. |
void | write(String s, int off, int len) | 문자열 s에서 off 위치부터 len길이만큼 버퍼에 저장합니다. |
+ close()에 경우 자바에서는 Garbage Collector/Collection에 의해서 내부 객체 및 리소스들이 정리되기 때문에 굳이 close()를 콜하지 않아도 이후에 자동으로 정리가 되어 문제가 발생하지 않습니다. 하지만 Garbage Collector가 실행되고 완료될때까지 스트림에 할당된 리소스들이 그대로 유지되기 때문에 최적화 관점에서 close()를 콜해 스트림을 종료해주는 것이 좋습니다.
+ write(int c)에 경우 만약 숫자 8을 파라미터로 입력하면 8이 버퍼에 저장되는 것이 아니라 8을 가르키는 아스키코드, 유니코드의 값이 버퍼에 저장되는 것입니다.