PS(Problem Solving)을 하는 중에 항상 입력을 Scanner로 하다가 시간 초과를 만났다.

그동안 입력은 Scanner, 출력은 System.out.println()을 고정적으로 사용해왔는데, 이와 비교했을 때 입출력 효율이 훨씬 좋은 BufferedReader/BufferedWriter을 이용하여 입/출력을 하는 방법에 대해 정리해보도록 한다.

 

먼저, 두 함수에 공통적으로 적혀있는 "Buffer"는 무엇일까?

버퍼(buffer)는 데이터를 전송할 때, 그 과정에서 일시적으로 데이터를 보관하는 임시 메모리 영역이다.

 

Scanner와 같이 버퍼를 이용하지 않는 입력의 경우 키보드의 입력이 있을 때 즉시 키보드에서 프로그램으로 데이터가 전송되고,

BufferedReader와 같이 버퍼를 이용하는 입력의 경우 키보드의 입력이 있을 때마다 한 문자씩 버퍼에 전송되고, 버퍼가 가득 차거나 개행 문자("\n"과 같은)가 나타나면 버퍼에 있던 내용을 한번에 프로그램에 전송한다. 

 

결론적으로, 버퍼를 사용한 입출력이 그렇지 않은 입출력보다 속도가 빠른 것을 기억하고, 어떻게 사용하는 지를 정리해보자. 

백준 문제 번호 15552번 "빠른 A+B"를 통해 설명하도록 한다. (맨 아래 풀이가 있다)

https://www.acmicpc.net/problem/15552

 

15552번: 빠른 A+B

첫 줄에 테스트케이스의 개수 T가 주어진다. T는 최대 1,000,000이다. 다음 T줄에는 각각 두 정수 A와 B가 주어진다. A와 B는 1 이상, 1,000 이하이다.

www.acmicpc.net

Theme. 입력! BufferedReader vs Scanner

BufferedReader를 이용한 입력을 설명하기에 앞서 주로 사용하던 Scanner를 사용하여 위 문제의 입력 부분 중 일부인 A=1, B=1을 입력받기 위해 코드를 작성해보면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Scanner// Scanner 클래스 호출
 
public class Main {
    public static void main(String[] args){
        // Scanner 객체 생성
        Scanner sc = new Scanner(System.in); 
        
        // 정수 A,B 입력 및 리턴 
        int A = sc.nextInt();
        int B = sc.nextInt();
 
        System.out.println(A); // 1
        System.out.println(B); // 1
        
    }
}
cs

와 같이 작성할 수 있다.

잠시 Scanner에 대해 정리해보자.

Scanner의 입력 메소드들은 공백(띄어쓰기) 또는 개행("\n" 등)을 기준으로 읽는다. 

즉 위 예제에서 "1 1"을  콘솔에 입력했을 때, 1과 다음 1 사이에 공백(띄어쓰기)가 존재하므로 A에 1이 입력되고,

두 번째 1을 입력 후 enter를 쳤을 때(개행) B에 1이 입력되게 된다. 

위 문제에서는 정수형 변수 A, B에 값을 입력받았으므로 Scanner의 입력 메소드 중 nextInt()를 사용했지만,
만약 String의 값을 입력받고자 한다면, next() 또는 nextLine()을 사용할 수 있다.
1
2
3
4
int n = nextInt(); 
double d = nextDouble();
String s1 = next(); // 공백(띄어쓰기)를 기준으로 한 단어씩 읽음
String s2 = nextLine(); // 개행을 기준으로 한 줄씩 읽음
cs

이때, 한 가지 주의할 점은 char의 값을 바로 입력받는 메소드는 없다(nextChar() 이런 메소드는 없다)

next()와 nextLine()의 차이를 간단한 예를 통해 확인해보자.

next()

next()공백을 기준으로 한 단어씩 읽다보니, "완벽하지 않은 것들에 대한 사랑"이라는 문장에서 "완벽하지"까지만 읽고 이를 변수 str에 저장했음을 알 수 있다.

next()를 이용해서 위 문장을 전부 읽기 위해서는 다음과 같이 코드를 작성할 수 있을 것이다.

하지만, 이와 같은 방법은 입력하는 문자열의 공백이 몇 개인지를 파악해야 한다는 불편함이 존재한다.

이에 개행을 기준으로 한 줄씩 읽는 nextLine()메소드를 사용하면, 

한 줄 전체가 str에 입력된 것을 확인할 수 있다.

 

이제 BufferedReader를 이용한 입력 방법에 대해서 정리해보자.

 

BufferedReader의 가장 큰 특징은 입력받은 데이터가 String으로 고정되어 있다는 것이다. 그래서 다른 type으로 입력을 받기 위해서는 형변환을 해야 하고, 이때 예외처리를 반드시 해줘야 한다.

예외처리를 위해 main 메소드에 throws IOException를 추가하는 것이 일반적이다. 

 

또한, BufferedReader는 한 줄씩(개행 기준) 읽게 된다.

 

또한, 입력은 readLine() 메소드를 사용한다.

이를 코드로 작성해보면,

1
2
3
4
5
6
7
8
9
10
11
12
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();
        int i = Integer.parseInt(br.readLine());
 
    }
}
cs

 

읽은 데이터들은 한 줄 단위이기 때문에 Scanner에서 공백을 기준으로 한 단어씩 읽는 경우와 동일한 결과를 가져오기 위해서 추가적인 작업이 필요하다. 

이때 사용하는 것이 StringTokenizer 또는 String.split()이다.

StringTokenizer의 nextToken() 함수를 통해 공백을 기준으로 한 단어씩 읽을 수 있게 해주거나,

String.split()을 통해 문자열 배열을 만들고, 인덱스별 각 원소에 한 단어씩 저장하는 것이다.

이를 코드로 작성해보면,

1. StringTokenizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
 
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int A = Integer.parseInt(st.nextToken());
        int B = Integer.parseInt(st.nextToken());
        
        // 콘솔에 5 4라고 입력하면?
        
        System.out.println(A); // 5
        System.out.println(B); // 4
        
    }
}
cs

2. String.split()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
 
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str = br.readLine();
        String[] arrStr = str.split(" ");
 
        // 콘솔에 5 4라고 입력하면?
        System.out.println(arrStr[0]); // 5
        System.out.println(arrStr[1]); // 4
    }
}
cs

 

Theme. 출력! BufferedWriter vs System.out.println()

먼저, 간단한 예시를 살펴보자.

일반적인 출력문인 System.out.println()은 데이터의 양이 적을 때의 출력에는 효율성을 따질 필요가 없이 유용하게 사용할 수 있다. 하지만, 출력해야 할 데이터의 양이 많아졌을 때는 버퍼를 사용한 출력을 하는 것이 효율적이다.

버퍼를 사용하는 출력이 BufferedWriter이다.

위 코드에서 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 부분이 출력을 위해 객체를 생성한 부분이다. 

br.readLine()을 통해 입력 받은 문자열을 StringTokenizer와 nextToken()을 통해 각각 str1, str2라는 변수에 저장했다.

str1과 str2에 저장된 데이터를 출력하기 위해서 bw.write(str1), bw.write(str2)를 작성한 것이다.

즉, write()를 통해 출력을 하는 것이다. 

System.out.println()에서는 개행의 효과가 있지만, write()에는 개행의 효과가 없다.(마치, System.out.print()와 같은 효과)

따라서, newLine()을 통해 개행을 해줄 수 있다.

write()를 통해 출력을 다 하고 나면, flush(), close()를 통해 출력 스트림을 닫도록 한다.

 

이제, 백준 15552번: 빠른 A+B를 푼 코드를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.StringTokenizer;
 
public class Main {
    public static void main(String[] args) throws IOException {
 
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        
        int T = Integer.parseInt(br.readLine()); // 테스트케이스의 개수 T 입력받음
        //출력할 때, 한 줄씩 합을 구하고 개행해줌
        for (int i = 0; i < T; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine()); // 1 1 입력 받고 A, B에 각각 저장하기 위함
            bw.write(Integer.parseInt(st.nextToken()) + Integer.parseInt(st.nextToken()) + "\n");
        }
        bw.flush();
        bw.close();
 
    }
}
cs

결과는

 

'Java' 카테고리의 다른 글

Singleton 간단하게 구현해보기  (0) 2023.02.09
HashMap 간단한 사용법  (0) 2023.02.08
String - Java  (0) 2023.01.15
참조변수 super, 생성자 super()  (0) 2022.12.31
오버라이딩(overriding)  (2) 2022.12.30

+ Recent posts