JAVA - IO(BufferedReader/BufferedWriter)

코딩테스트? 퀴즈를 풀면서 해결한 이후엔 다른 사람의 코드와 비교해보곤 하는데, 대부분의 사람들이 io에 buffer를 이용한걸 확인했다. 이전에도 코딩테스트에서는 Scanner 객체를 사용하는대신 buffer를 이용해야 효율이 좋다는 이야기를 들었고, 빡빡하게 진행하는 데에서는 아예 Scannerprint를 사용하면 아예 통과하지 못한다고도 들은바는 있었다.

그러나 buffer를 잘알지 못하다보니 당장은 문제를 해결하기 위한 로직에만 집중하자며, 미뤄두었는데 이제서야 buffer를 좀 정리해보려고 한다.

먼저 buffer를 왜 사용해야하는지부터 알아보자.


왜 버퍼(buffer)를 사용해야 할까?

buffer를 사용하지 않을 경우엔 사용자가 입력하는 키가 곧바로 프로그램에 전달된다. 이렇게 되면 사용자가 길게 입력할수록 키보드에서 프로그램으로 전달되는 횟수가 증가하며 프로그램의 성능저하를 유발할 수 있다.

반면 buffer를 사용할 경우엔, 일단 사용자가 입력을 마칠 때까지 buffer에 임시로 담아뒀다가 입력이 끝나면, 한꺼번에 buffer에 있는 값을 프로그램으로 전달한다.

여기서 버퍼란 데이터를 어떤 지점에서 다른 지점으로 전송하는 동안 일시적으로 데이터를 보관하는 메모리의 영역을 말한다. 버퍼의 또 다른 이름은 큐(Queue)라고 한다.
출처 : 위키


StreamReader과 Buffer

그동안 사용했던 Scanner 는 사실 원래 존재하지 않던 객체이다. Java 1.5에서 처음 적용되었으며, 좀 더 간편하게 콘솔에 문자를 출력하기 위해 개발되었다고 한다. Stream이 Scanner에 비하면 불편할 수 있지만, 원래 오리지널 방식의 입출력 객체라는 이야기이다.

Stream을 사용하기 위해서는 예외처리를 반드시 해주어야 한다. 출력할 때마다 try catch() 를 사용할 수도 있지만, main 메서드에 IOException 을 처리해두면 편하게 사용할 수 있다. 이 IOException 클래스도 java.io 에 내장되어 있다.

1
2
3
4
5
6
7
8
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
class devandy {
public static void main(String[] args) throws IOException {
// buffer 사용 준비 완료!
}
}

InputStreamReader 로는 한글자씩밖에 입력받지 못한다고 한다. 그래서 몇자를 입력해도 한 글자만 출력한다. InputStreamReaderOutputStreamWriter만 사용해서 IO를 시도할 경우, 다음과 같은 결과를 확인할 수 있다.

입력으로 “12345” 를 입력했지만 정작 출력되는 1과 왜 출력된지 알 수 없는 %뿐이다.

이번엔 BufferedReaderBufferedWriter를 사용해서 입력과 출력을 해보겠다.

한글자밖에 입력받지 못했던 InputStreamReaderBufferedReader로 감싸서 객체를 생성하니 문자열 그대로를 가져와서 출력까지 성공했다. (여전히 %는 왜 출력되는지 모르겠다.)

원리는 다음과 같다.

`InputStreamReader(System.in)` : 콘솔에서 입력한 문자를 문자 스트림으로 변환, 주의할 점은 InputStreamReader는 정수형 데이터만 입력받는다.

위의 경우처럼 다른 데이터타입으로 입력받으려면 InputStreamReader 객체를 다른 형변환해주는 객체로 감싸줘야 한다.

1
2
InputStreamReader isr = new InputStreamReader(System.in);
String number = String.valueOf(isr.read());

Buffer를 이용하여 입출력을 할 때는 String만 입출력이 되므로 다른 데이터를 입출력하고자 한다면, 형변환해서 사용해야 한다.


buffer이용한 io 실습

간단하게 코드를 작성해보자. 백준의 문제중 2908번 문제를 활용해보겠다.

문제를 요약하면, 두 수(세자리 수)를 한 줄로 입력받아 각각의 수의 백의자리 숫자와 일의자리 숫자의 위치를 바꾼 후, 두 수의 크기를 비교하여 더 큰 수를 출력하는 문제이다.

이 문제에 대해 내가 작성한 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.Scanner;

class Main {
int changeValue(String num){
String[] temp = num.split("");
String changed = "";
changed += temp[2];
changed += temp[1];
changed += temp[0];

return Integer.parseInt(changed);
}

public static void main(String[] args){
Main m = new Main();
Scanner sc = new Scanner(System.in);

String nums = sc.nextLine();
sc.close();
String[] divide = nums.split(" ");

if(m.changeValue(divide[0])>m.changeValue(divide[1])){
System.out.println(m.changeValue(divide[0]));
} else {
System.out.println(m.changeValue(divide[1]));
}
}
}

그러나 이 코드는 Scanner 객체를 사용하여 프로그램 성능이 떨어진다. 이 코드에서 io에 해당하는 코드를 buffer를 이용한 io 방식으로 바꿔보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
// StreamReader는 한글자씩밖에 저장을 할 수 없다고 한다.
// 그래서 BufferedReader를 객체로 생성해서 입력받은 문자를 buffer에 저장한다.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;

class Main {
int changeValue(String num){
String[] temp = num.split("");
String changed = "";
changed += temp[2];
changed += temp[1];
changed += temp[0];

return Integer.parseInt(changed);
}

public static void main(String[] args) throws IOException {
Main m = new Main();
BufferedReader br
= new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw
= new BufferedWriter(new OutputStreamWriter(System.out));

String nums = br.readLine().trim();
br.close(); // BufferedReader 종료
String[] divide = nums.split(" ");

if(m.changeValue(divide[0])>m.changeValue(divide[1])){
bw.write(String.valueOf(m.changeValue(divide[0])));
bw.flush(); // buffer에 남아있는것 모두 클라이언트로 전송후, 비운다.
} else {
// 출력을 위해서 String으로 변환해서 출력
bw.write(String.valueOf(m.changeValue(divide[1])));
bw.flush(); // 위와 마찬가지
}
bw.close(); // BufferedWriter 종료
}
}

바꾼 코드로 다시 제출해봤다. IDE에서 코딩하지 않고, 마크다운 에디터(Typora)에서 코딩하고 확인업싱 바로 제출하느라 두번의 컴파일 에러가 있었다. 정상적으로 제출한 코드의 결과를 보니 시간이 100ms에서 80ms로 20%의 효율이 발생한 것을 확인할 수 있었다.

오늘은 조금 간략하게 정리했는데, 더 Stream에 대해 더 공부하고 파일 입출력과 함께 정리해보겠다.