Today
-
Yesterday
-
Total
-
  • 💡 [Kotlin] 빠른 입출력(I/O) - BufferedReader, BufferedWriter (예제 : BOJ 15552)
    | 프로그래밍 분야/Kotlin 2021. 7. 25. 20:48

    Java에는 Scanner.next()System.out.println()기본적인 입출력 함수들이 존재합니다.

     

    마찬가지로, Kotlin에는 readLine()println()기본적인 입출력 함수들이 존재합니다.

     

    (물론, Kotlin에서 스캐너와 sysout을 써도 됩니다. 또, kotlin.io.println()은 System.out.println()과 100% 동일한 함수입니다.)

     

    위 함수들은 사용이 간편한 대신, 느린 입출력(slow I/O)에 해당합니다.

     

    왜 느릴까요?

     

    우리는 보통 (Java에서)이렇게 스캐너를 정의하여 사용합니다.

    Scanner sc = new Scanner(System.in);

    여기에서 System.in은 InputStream타입의 정적 필드입니다. 그래서 Scanner의 생성자로부터 System.in.read()를 통해 콘솔에서 문자를 1바이트씩 입력받고 어쩌구 저쩌구...

     

    자세한 설명은 다른 분이 논문급으로 콘솔 입력을 정리해 둔 글이 있어서 이로 대체합니다.

     

    JAVA [자바] - 입력 뜯어보기 [Scanner, InputStream, BufferedReader]

    이 글을 지금 이 시점에 써야 할까 고민을 많이 했다. 사실 자바를 그냥 다룰 줄만 아는 것에 목표를 둔다면 이 글이 무의미할 수도 있다. 그러나 자바에 대해 조금이라도 관심이 있고 더 배우고

    st-lab.tistory.com

    한 줄로 요약하자면, Scanner는 수많은 자바 정규식을 검사하는 과정에서 심각한 비효율이 발생합니다.

     

    println이나 readLine도 비슷한 맥락에서, 내부적으로 많은 공정이 이루어지므로 처리 속도가 굉장히 느립니다.

     

     

     

    그렇다면, 빠른 입출력에는 어떤 것들이 있을까요? 바로 BufferedReaderBufferedWriter입니다.

     

    BufferedReader가 빠른 이유, 즉 Scanner와 다른 점은 다음과 같습니다.

     

    1. 정규식을 확인하지 않고 바로 처리합니다.

     

    2. 입력을 그때그때 처리하는 Scanner와 달리, 버퍼를 통해 처리 시점을 개발자가 직접 지정해줄 수 있습니다.

     

    이를 염두에 두고, 다음 예제를 살펴보겠습니다.

     

     

    백준(BOJ) 15552 - 빠른 A+B

    예제 입력 예제 출력
    5
    1 1
    12 34
    5 500
    40 60
    1000 1000
    2
    46
    505
    100
    2000

     

    입력의 첫 줄에 테스트케이스의 개수 T가 주어집니다. T는 Int형의 범위를 넘어가지 않으며, 다음 T줄에는 각각 두 정수 A와 B가 주어집니다. A, B, A+B는 모두 Int형의 범위를 넘어가지 않습니다.

     

    출력에서는 각 테스트케이스의 A+B가 한 줄에 하나씩 순서대로 출력됩니다.

     

    다음은 예시 코드입니다.

    import java.io.BufferedReader
    import java.io.BufferedWriter
    import java.io.InputStreamReader
    import java.io.OutputStreamWriter
    import java.util.StringTokenizer
    
    fun main() {
        val br = BufferedReader(InputStreamReader(System.`in`))
        val bw = BufferedWriter(OutputStreamWriter(System.out))
        repeat(br.readLine().toInt()) {
            val token = StringTokenizer(br.readLine())
            val sum = (token.nextToken().toInt() + token.nextToken().toInt()).toString()
            bw.write(sum + "\n")
        }
        bw.flush()
        bw.close()
    }

    한 줄씩 차근차근 살펴보겠습니다.

     

    • BufferedReader 객체를 생성하여 br 변수에 주소를 할당합니다.

    • 마찬가지로 bw에도 BufferedWriter 객체를 할당해줍니다.
    • 테스트케이스의 개수를 받기 위해 br.readLine() 메서드를 통해 버퍼에서 입력을 받고, Int형으로 변환해서 repeat 메서드로 테스트케이스를 반복합니다.
    • br.readLine()을 통해 정수 두 개를 입력받습니다(현재는 사실 정수가 아니라 String). StringTokenizer에 넘겨주면 공백을 기준으로 Token으로써 String을 split해줍니다.
    • 두 개의 토큰을 받아 각각 Int형으로 변환 후 합연산을 해준 뒤, 다시 String으로 변환하여 변수 sum에 대입합니다. 이런 과정을 거치는 이유는, 뒤에 사용할 BufferedWriter(bw)의 write() 함수가 Int형 파라미터를 '숫자'가 아닌 10진수 인코딩 값, (정확히는 다르지만)쉽게 말하면 '아스키 코드'값으로 처리하여 정상적인 값이 출력되지 않기 때문입니다.

    • sum 문자열과 개행을 bw.write() 메서드를 통해 Output Buffer에 올려놓습니다. (이 때 콘솔에 출력되는것이 아닌, 버퍼에 올라가만 있는 상태임!!)

    • 이렇게 repeat을 돌며 버퍼에 올라간 문자열들을 나중에 한 번에 bw.flush()메서드를 통해 출력해줍니다. repeat 메서드 안에 flush를 두지 않는 이유는 그 때 그 때 출력해주게 되면 백준에서 타임아웃이 뜨기 때문....

    • 버퍼는 사용 후에는 꼭 close()해주는 습관을 가집시다. 닫아주지 않아도 백준에서 문제를 푸는 데에는 영향이 없긴 합니다.
sangilyoon.dev@gmail.com