
/* 본 글은 개인적으로 공부한 내용을 정리한 글이므로 오류가 있을 수 있습니다. */
0.1. 참고 자료
[빡센] 코틀린 8. 입력과 출력 : https://bbaktaeho-95.tistory.com/11
알고리즘 테스트를 보면 값을 입력 받고 처리하는 식의 문제가 많다. 이번에는 코틀린으로 입력을 처리하는 방법에 대해 알아보자. 크게 입력을 받는 방법은 두개가 있다.
1. readLine() 함수 사용
<kotlin />
fun main(args:Array<String>){
print("입력할 값 :")
val value = readLine()
println("입력 값 : $value")
}

readLine() 함수를 사용하게되면 반환 값을 String? 형태로 반환한다. 필요시 형변환을 통해 필요한 데이터 형태로 바꾸면 된다. (toInt, toDouble 같은 함수를 이용해 파싱)
여러 변수들을 받고 싶을 때는 어떻게 해결하면 좋을까?
<kotlin />
fun main(args:Array<String>){
print("입력 크기 : ")
val size: Int = readLine()!!.toInt()
val arr = Array<Int>(size) { readLine()!!.toInt() }
var sum = 0
for (item in arr) {
sum+=item
}
println("Sum: $sum")
}
배열을 이용해서 해결 했다.
2. Scanner 사용
Java에서 사용하는 것처럼 Scanner 객체를 만들어 사용할 수 있다.
<kotlin />
import java.util.Scanner
fun main(args:Array<String>){
//2. Scanner()
val sc = Scanner(System.`in`)
print("입력할 값: ")
val value3 = sc.nextInt()
val value4 = sc.nextInt()
println("입력 값: $value3, $value4")
}

next() 함수를 이용하면 문자 또는 문자열을 공백을 기준으로 입력받는다. 그렇기 때문에 1 2 3 4 로 입력했을 때 nextInt()를 이용해 파싱할 수 있다.
readLine() 과 마찬가지로 nextLine()은 한 줄 전체를 String 타입으로 입력 받는다.
1. 추가 공부 ( 2021 / 09 / 24 )
입력에 관해 더 자세히 알아보고자 자바 입력 / 버퍼 입출력에 대해 추가적으로 공부했다.
[st-lab] Java - 입력 뜯어보기(Scanner, InputStream, BufferedReader) : https://st-lab.tistory.com/41
[Dev.Meoru for 안드로이드] Kotlin - 빠른 입출력(I/O) : https://meoru-tech.tistory.com/57
*앞으로 쓸 내용들은 위 참고 자료로 부터 나온 내용들을 개인적으로 요약한 자료입니다. 자세한 정보를 원한다면 위 링크로 들어가 읽어보길 바랍니다. (첫 번째 자료 강추)
1.1. 1. Java 인코딩
*인코딩이 무엇인지 모른다면? 이 글([CS] 인코딩을 참고하자)
Java는 String 을 처리할 때 내부에서는 UTF-16BE 인코딩으로 문자열을 저장하고, 송수신에서는 직렬화가 필요한 경우 변형된 UTF-8 을 사용한다. 문자열 입/출력 할때에만 사용자가 지정한 인코딩 값과 또는 운영체제의 인코딩 값으로 문자열을 인코딩한다. UTF-8 기준 (editor file endoing 설정에 따라 달라진다.)
*입력(UTF-8) -> 송수신(modified UTF-8) -> 자바 메모리 (UTF-16) -> 송수신(modified UTF-8) -> 출력(UTF-8)
UTF-8 / UTF-16 차이
기본적으로 문자의 영역에 따라 사용하는 Byte 가 다르다. UTF-8 의 경우 영어는 1Byte 한글의 경우는 3Byte를 사용한다.
UTF-16은 거의 모든 문자가 2Byte 를 사용한다.
cf > 1 ~ 127 까지는 Ascii 코드 값과 유니코드(UTF-8, UTF-16 등..), MS계열 코드(CP949, MS949 등..) 의 값이 같다.( ms 랑 유니코드는 해당 범위에서 92 번만 다른데 이는 역슬래시로 윈도우에서는 대부분 ₩ 으로 표현되고 맥북, 리눅스 계열에서는 \ 으로 표현된다.)
<kotlin />
val a = 'a'.toInt() // 97
val b = '가'.toInt() // 44032
char 'a' 를 Int 값으로 변환할 때 나온 97은 "아스키 코드 값" 이라고 이해하기 보다는 파일 인코딩 형식의 10진수 값이 나온다는 것이 정확하다.
2. Stream(스트림)
입/출력 을 완벽히 이해하기 위해서는 스트림을 알아야한다. 일단 'Stream' 의 뜻은 시내(n), 흐르다(v) 이다. 그렇다면 컴퓨터에서 Stream은 무엇을 의미할까?
쉽게 말하자면 "데이터 통로"이다.

*한 곳에서 다른 곳으로의 데이터 흐름을 스트림이라고 한다.
스트림은 단반향이다. 입/출력 말고도 파일 데이터, HTTP 응답 데이터 등도 스트림의 예시라고 할 수 있다.
InputStream
inputStream 은 자바에서 가장 기본이 되는 입력 스트림(통로)이다.
<kotlin />
import java.util Scanner
fun main(){
val sc = Scanner(System.in
)
}
여기서 Scanner에 넣어주는 System.in
은 InputStream 타입의 필드이다. System 클래스를 확인해보면 다음과 같다.
<kotlin />
public final class System {
public static final InputStream in;
...
}
System class 에 있는 in 이라는 필드는 InputStream 의 정적 필드이다.
즉, Scanner 에 넣어주는 System.in
은 통로(inputStream)라고 생각하면 된다.
특징
- 입력받은 데이터는 int 형으로 저장되는데 이는 10진수의 UTF-16 값으로 저장된다.
- 1 byte 만 읽는다.
1.2. 3. InputStreamReader
<kotlin />
import java.util Scanner
fun main(){
val sc = Scanner(System.in
)
}
위 코드에서 일어나는 과정을 설명해보자. 먼저 Scanner 클래스의 생성자로 주입해주는 System.in
은 다음 Scanner 클래스를 생성한다.
<kotlin />
public Scanner(InputStream source) {
this((Readable)(new InputStreamReader(source)), (Pattern)WHITESPACE_PATTERN);
}
위 코드를 보면 Scanner(Readable source, Pattern pattern) 으로 넘겨진다.
여기서 InputStreamReader 가 나온다. InputStream의 특징에서 1byte 만 읽는다는 것을 알 수 있다. 이 때문에 InputStream은 문자를 온전하게 받아오는 것이 불가능한 경우가 생긴다. (출발점에서는 3byte 를 보냈는데 도착점에서 2byte를 스트림에 남기고 1byte만 받아가기 때문에 이를 변환하면 다른 문자로 변환됨.)
이러한 한계를 극복하고자 나온 것이 InputStreamReader 이다. 이는 문자를 온전히 받기 위해 InputStream 을 확장한 것이다. 문자를 온전히 받을 수 있는 InputStreamReader 를 문자 스트림이라고 부른다. 즉, 바이트 단위로 InputStream이 데이터를 받으면 InputStreamReader가 바이트를 묶어서 char 형태로 데이터를 처리해 변환한다.
특징
- 바이트 단위 데이터를 문자(character) 단위 데이터로 처리할 수 있도록 변환해준다.
- char 배열로 데이터를 받을 수 있다.
1.3. 4. Scanner 메서드 원리
우리는 입력을 처리하기 위해 Scanner 의 next(), nextInt() , nextDouble() 등 메서드를 썼다. 그러면 이러한 메서드의 과정은 어떻게 될까?
과정은 다음과 같다. (자세한 과정은 첫 번째 참고자료에 아주 자세히 나와있다!)
- InputStream (바이트스트림) 을 통해 입력 받음
- 문자로 온전하게 받기 위해 중개자 역할을 하는 InputStreamReader(문자스트림) 을 통해 char 타입으로 데이터를 처리함
- 입력받은 문자는 입력 메소드( next(), nextInt() 등등.. ) 의 타입에 맞게 정규식을 검사함
- 정규식 문자열을 Pattern.compile() 이라는 메소드를 통해 Pattern 타입으로 변환함
- 반환된 Pattern 타입을 String으로 변환함
- String 은 입력 메소드의 타입에 맞게 반환함 ( nextInt() - Integer.parseInt() / nextDouble() - Double.parseDouble() 등등.. )
* 중간에 타입에 맞는 정규식을 검사하는 과정에서 많이 검사하기 때문에 Scanner의 속도가 느리다.
1.4. 5. BufferedReader
Scanner 보다 좀 더 빨리 입력을 처리할 수는 방법은 없을까?
BufferedReader를 사용하면 된다. BufferReader는 버퍼를 통해 입력받은 문자를 쌓아둔 뒤 한 번에 문자열처럼 보내버린다. readLine() 메서드를 통해 String 형으로 데이터를 받을 수 있다.
기본적으로 bufferReader를 사용하는 코드는 다음과 같다.
<kotlin />
val br = BufferedReader(InputStreamReader(System.in
)
위 코드를 보면 다음과 같은 사실을 알 수 있다.
- 기본적으로 바이트 스트림인 InputStream을 통해 바이트 단위로 데이터를 입력 받는다.
- 입력 데이터를 char 형태로 처리하기 위해 중개자 역할인 문자스트림 InputStreamReader를 사용한다.
InputStream -> Byte Type -> InputSreamReader -> Char Type -> BufferReader -> String Type (char 의 직렬화)
byte 타입으로 읽어들이는 in을 char 타입으로 처리한 뒤 String, 즉 문자열로 저장할 수 있게 한다는 의미로 해석할 수 있다.
특징
- 버퍼가 있는 스트림이다.
- 정규식 검사를 따로 하지 않고 문자열(공백 포함)을 보내기 때문에 Scanner에 비해 성능이 좋다.
- 디폴트로 8192개의 문자를 저장할 수 있다. (변경가능)
- 기본적으로 개행이 입력되거나 flush()를 쓰거나 버퍼가 꽉 차게 되면 버퍼를 비우면서 프로그램으로 데이터를 보낸다.
하나하나 문자를 보내는 것이 아닌 한 번에 모아둔 다음 보내니 훨씬 속도가 빠르고 별다른 정규식을 검사하지 않으니 더더욱 속도는 빠를 수밖에 없다.
즉, 정리하자면 바이트 단위 [InputStream]로 문자를 입력받아 문자(character) [InputStreamReader]로 처리한 뒤 버퍼(buffer) [BufferedReader]에 담아두었다가 일정 조건이 되면 버퍼를 비우면서 데이터를 보내는 것이다.
Buffered Reader / Writer 사용 예시 (백준 15552번 - 빠른 A + B)
<kotlin />
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()
}
*StringTokenzer() 는 공백 기준으로 split 해줘서 token 이 담긴 배열을 반환.
주요 개념 : Stream , 인코딩
이제 알고리즘 풀 때 시간 단축할 수 있겠다.