12 분 소요

원리를 알면 바위도 깰 수 있다 ..!


1. 자바 인코딩

인코딩 환경과 디코딩 환경이 다를 때 웹사이트에서도 한글이 깨지거나, 터미널에서 한글이 깨지는 그런 상황을 종종 경험해 봤을 것이다.

인코딩이란?

사람의 문자를 컴퓨터가 이해하기 쉽게 변환하는 과정

캐릭터 셋 이름

캐릭터 셋 이름 설명
US-ASCII 7비트 아스키
ISO-8859-1 ISO 라틴 알파벳
UTF-8 8비트 UCS 변환 포맷. 글자의 길이가 가변적인 인코딩 방식, 1byte는 아스키코드 3byte는 한글, 웹서버, db, Linux,Mac 시스템의 기본 인코딩 방식
UTF-16BE 16비트 UCS 변환 포맷. big-endian 바이트 순서를 가진다.
UTF-16LE 16비트 UCS 변환 포맷. little-endian 바이트 순서를 가진다.
UTF-16 16비트 UCS 변환 포맷. 바이트의 순서는 byte-order mark라는 것에 의해서 정해진다. 글자의 길이가 고정적인 인코딩 방식
EUC-KR 8비트 문자 인코딩으로, EUC의 일종이며 대표적인 “한글 완성형” 인코딩
MS949 Microsoft에서 만든 “한글 확장 완성형” 인코딩, 비표준, ASCII 코드가 포함
  1. 자바는 내부적으로 (메모리 상에서) 문자열이 UTF-16 BE 로 인코딩 되어 처리됨

  2. 문자열 송/수신을 위해 직렬화를 할땐 변형된 UTF-8 사용

  3. 문자열을 입출력 할 때에는 운영체제 기본 인코딩값, 또는 사용자가 지정한 인코딩 값으로 문자열을 인코딩 한다. (1번처럼 내부 메모리 상에서 처리되는 것과는 다름)

  4. 1~127까지는 ASCII 값과 유니코드(UTF-8, UTF-16 … ), MS계열(MS949)와 값이 다 같다. (MS와 유니코드는 해당범위에서 92번만 다르다. 이는 역슬래시로 윈도우에서는 ₩으로 표현, 맥북,리눅스 계열에서는 역슬래시로 표현된다)

UTF-8과 UTF-16의 차이점은 문자 하나를 표현하기 위한 필요 bit의 크기로 나뉜다.

UTF-8과 UTF-16 이 처럼 뒤의 숫자는 몇 bit를 사용하여 Index를 표현할 것인가를 뜻한다.

UTF-8 은 8Bit 가 1개의 Index

UTF-16은 16bit가 1개의 Index

또, 문자 하나를 표현하기 위한 byte 범위의 차이로도 나뉜다.

UTF-8의 경우 한 문자를 나타내기 위해 1byte-4byte를 사용한다. 사실은 6byte까지 사용하지만 일반적인 문자는 3byte내로 처리되며, 4byte 영역은 이모티콘 같은 문자가 존재한다. 사실상 1byte 이상의 문자를 사용하는 경우가 없다보니 1~4byte를 사용한다고 말한다.

영문 byte 수 : 1byte

한글 byte 수 : 3byte

UTF-16의 경우 2byte~4byte를 사용한다. 대부분 2byte이고 4byte경우는 거의 쓰지 않는다.

영문 byte 수 : 2byte

한글 byte 수 : 2byte

인코딩 처리 방식을 보면,

x로 표시된 부부분은 원래 비트 값을 순서대로 적으면 되는 거고,

어떤 문자가 800에 대응 되면 1110xxxx 10xxxxxx 10xxxxxx으로 변환되는 것이다.

자바는 내부에서 UTF-16을 쓴다고 했다. 그 이유는 인코딩 할 때 널(NULL) 문자가 나타나지 않기 위해서다.

그래서 U+0000을 2Byte로 구성한다.

즉, 자바 메모리에 올라갈 때 과정을 설명하면

ex) 이클립스 File encoding이 UTF-8이라면 ,

입력(UTF-8) -> 송수신(modified UTF-8) -> 자바 메모리(UTF-16) -> 송수신(modified UTF-8) -> 출력(UTF-8)


  1. 직렬화란 객체를 기록할 수 있는 포맷으로 변환하여 파일로 저장하거나, 다른 시스템과 송수긴을 가능하게 하는 기법 즉, 객체 또는 객체의 상태를 쉽게 옮길 수 있는 형태로 변환하는 과정이다.

  2. 대부분의 인코딩 형식들은 해당 값과 아스키 코드 값이 10진수로, 1~127번까지 대응하는 문자가 같다.

    코딩할 때 char 문자에 저장하여 int값을 반환할 때, 나온 숫자가 아스키 코드 값 이라고 대부분 알고 있다. 그것은 어디까지나 127번째 문자 까지이고, 정확히는 파일 인코딩 형식의 10진수 값이 나온다고 생각해야한다.


InputStream 과 System.in

먼저 스트림을 이해하고 가야한다.

스트림(stream)

자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룹니다.

스트림(stream)이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미합니다.

즉, 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할을 합니다.

스트림은 출발지와 도착지를 이어주는 빨대라고 생각하면 좋다.

위 처럼 한 곳에서 다른 한 곳으로의 데이터 흐름을 스트림이라고 한다.

그리고 스트림은 단방향 이라서, 입력과 출력이 동시에 발생할 수 없다. 그렇기 때문에 용도에 따라 입력스트림, 출력스트림이 나뉜다.

고속도로에서 자동차가 역주행 할 수 없듯이, 스트림은 단방향으로만 흐르며

중앙분리대 혹은 도로 자체가 분리되어 상향행 하향행이 존재하듯, 입력스트림과 출력스트림 또한 분리되어 있다.

자바에서 제일 기본이 되는 입력 스트림은 InputStream 이다 .(이름 그대로..! 들어오는 스트림)

반대로는 출력 스트림(OutputStream)

스트림은 대충 이해가 가지만 …

근데 왜 InputStream과 System.in을 엮어서 쓰는 걸까?

이에 대답은 밑에 사진을 보자

System.in이 InputStream 타입의 필드라는 거다 !

더 정확히 보면, System 클래스의 in이라는 필드는 InputStream 의 정적 필드라는 것이다.

(System 클래스의 in 변수는 ‘표준 입력 스트림’이며, 일반적으로 콘솔, 명령줄 인수 등을 통해 입력 받을 수 있다. 걍 내가 키보드로 치거나 터미널 등에서 입력 넣어주는 거 자체가 System.in을 통해 연결된단 것 !)

정리하자면, in이라는 변수는 InputStream의 변수이고, 결국 InputStream 타입의 새 변수를 선언하고 그 변수에 System.in을 할당시킬 수도 있다는 거다.

이렇게 때문에 System.in과 InputStream을 같이 묶어서 설명하게 되는 것.

그럼 in이 inputstream의 변수를 새로 선언하는 거니까 저것만으로도 입력이 가능할까?

package bak;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {

        InputStream in = System.in;
        int a = in.read();
        System.out.println(a);    

    }

}

주의할점

기본적으로 입출력 클래스는 java.io 패키지에 있다. 그리고 반드시 예외처리가 필수다.

try-catch 혹은 throw IOException으로 처리해주자.

Scanner/System.out.print 메소드는 해당 메소드 안에서 자체적으로 예외처리 하기 때문에 예외처리 해줄 필요가 없지만, 기본적으로 io 패키지는 IOException이라는 예외를 던진다.

실행 시켜보면

결과가 이렇게 나온다.

다른 수를 넣어도

InputSream.read() 는 특징이 두가지가 있다.

  1. 입력받은 데이터는 int형으로 저장되는데 이는 해당 문자의 시스템 또는 OS의 인코딩 형식의 10진수로 변수에 저장한다.

  2. 1byte만 읽는다.

컴퓨터의 모든 데이터는 Byte 단위로 되어 있다.

즉, 데이터를 저장하던,전달하던 컴퓨터는 바이트 단위로 데이터를 저장한다.

InputStream은 바이트 단위로 데이터를 보내며 이 InputStream의 입력 메소드인 read()는 1byte 단위 로 읽어 들인다.

또한 바이트 단위로 읽어 들이면 입력 받은 문자가 2byte 이상으로 구성되어 있는 인코딩을 사용할 경우 1byte 값만 읽어들이고 나머지는 읽지 않고 스트림에만 남아있기 때문에 출력할 때는 해당 데이터의 1byte에 대한 인코딩 값을 10진수로 변환한 값이 출력되는 것이다.

추가 설명(클릭!)

10000001 00001111의 값을 갖고 있는 2Byte 문자가 있다. 로 나뉘어 스트림을 통해 10000001 / 00001111로 데이터가 흐르게 된다 즉, 스트림에서는 1Byte 데이터가 2개가 있다.

하지만 프로그램에서 read()를 한번만 쓰면, 먼저 입력된 10000001은 읽지만 뒤에 00001111은 스트림에 잔존한다. 만약에 나머지도 읽고 싶으면 한번 더 read() 를 써야한다.

이렇게 바이트 단위로 주고받는 스트림을 바이트 스트림이라고 한다.

그럼 10개 문자를 입력받으려면 10개 변수를 선언할까? 이럴 땐, 그래도 되지만 바이트 타입 배열을 선언하고 read()에 넣어서 입력받는 방법도 있다.

package bak;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {

        InputStream in = System.in;
        byte[] a = new byte[10];
        in.read(a);

        for(byte v : a) {
            System.out.println(v);
        }

    }

}

byte 배열을 선언한 뒤 read() 메소드에 배열을 넣으면 입력하면서 바이트 값으로 a 배열에 들어간다.

애초에 byte[]배열 말고는 다른 타입은 read 메소드에 넣을 수 없다(int,char)

출력하면 해당 문자가 아닌 운영체제 인코딩 방식의 10진수 값이 나온다.

이를 문자로 치환하고 싶으면 char로 캐스팅 해주면 된다.

그리고 한글을 제대로 인식하지 못한다는 단점이 있다.

아스키코드를 보면 255개의 문자가 있는데 한글이 없다. 1Byte의 범위는 -128~127이며, 마지막 1bit 가 남아있는 것을 활용하여 확장한 것이 128-255 범위의 문자 들이다.

그래서 한글을 입력하게 되면 아무리 캐스팅을 해주어도 엉뚱한 문자가 나온다.

package bak;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {

        InputStream in = System.in;
        int b = in.read();
        System.out.println((char)b);
        System.out.println(b);


    }

}

‘가’이라는 문자를 입력하니, e위에체크, 10진수로는 234가 나왔다.

나는 이클립스 file encoding을 UTF-8 인코딩을 쓰고 있다. UTF-8의 경우 한글은 3byte를 사용한다.

3byte로 구성되어 있고 ‘가’라는 문자는각 1Byte씩, 234, 176, 128의 구성이 합쳐져 ‘가’ 라고 표현된다.

즉, InputStream.read()는 1Byte씩 밖에 못읽기 때문에 234까지만 읽고, 나머지는 바이트 스트림에 남아있게 되는거다.

(옆의 UNICODE는 유니코드에서 코드 포인트라고 하는 값이다. 즉 ‘가’라는 문자가 유니코드에서는 16진수로 AC0이다. )

그리고 자바의 경우 UTF-16을 쓰는데, 234를 16진수로 변환하면 EA, 자바의 인코딩 방식에 따라 0xc3 0xaa로 변환되어 이에 대응되는 유니코드 테이블의 문자는 이와 같다.






(자바 내부 메모리 상에서는 유니코드 인코딩 규칙에 의해 2byte길이의 이진법으로 인코딩 되므로 11000011 10101010 이 저장된다. )

U+XXXX는 유니코드 코드 포인트, 0xXX 0xXX는 java에서 저장되는 이진수 값의 16진수

그래서 출력하면 11000011 10101010에 대응되는 즉 0xc3 0xaa 에 대응 되는 문자인 ê 가 출력되는 것이다.


요약

  • UTF-8로 입력을 받는다.

  • read() 메소드는 1byte만 읽기 때문에 나머지 byte는 스트림에 잔존하게 된다.

  • 읽어드린 byte값은 UTF-16에 대응되는 문자의 인코딩방식으로 2진수 값이 저장된다.

  • 출력시 메모리에 저장되어 있던 2진수에 대응되는 문자가 UTF-8로 변환되어 출력된다.


Scanner(System.in)과 InputStreamReader(System.in)

먼저 Scanner 클래스 부터 알아본다.

위를 통해

을 알았다.

우리가 평소 쓰는 Scanner은 다음과 같이 풀어쓸 수 있다.

package bak;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Main {

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

        int a = sc.nextInt();
        System.out.print(a);


    }

}

그런데 왜 Scanner()에 InputStream이 들어가는 걸까

Scanner 클래스를 보면서 이를 이해해보자.





Scanner 클래스 파일에는 Scanner라는 생성자가 오버로딩 됨을 볼 수 있다.

우리가 흔하게 쓰는 Scanner(System.in)은 System.in이 InputStream 타입이고, System.in 하나만 넣었으니 어디로 가는지 볼 수 있다.

바로 565번째 줄 public Scanner(InputStream source) 로 보내지는 것이다.

즉, 아래 코드가 우리가 흔히 쓰는 Scanner(System.in)의 경로이다.

public Scanner(InputStream source) {
    this(new InputStreamReader(source), WHITESPACE_PATTERN);
}

이 코드가 우리가 흔히 쓰는 Scanner(System.in)의 경로다.

근데 코드를 보면 여러 Scanner() 생성자 모두 가장 상위에 있는 Scanner 생성자로 간다.

즉, private Scanner(Readable source, Pattern pattern) 으로 넘겨진다.


이때 , new InputStreamReader(source)를 보내는데, 이 때 InputStreamReader가 나오는 것이다.



InputStreamReader를 먼저 보기 전,

InputStream의 가장 큰 특징 두 가지를 본다면

  1. 입력받은 데이터는 int형으로 저장되는데 이는 10진수의 UTF-16값으로 저장되더라

  2. 1byte만 읽는다

InputStream 의 경우 InputStream.read()를 통해 입력 받으려고 해도 1Byte만 인식하니 한글은 입력해봤자 읽지도 못하고, 더군다나 엉뚱한 문자까지 나온다. 이를 해결하기 위해선 입력받은 모든 바이트를 다 온전하게 읽어야한다. 그래서 이를 확장시킨것이 InputStreamReader.


아래의 자바 API 문서를 보자





InputStream의 바이트 단위로 읽어 들이는 형식을 문자단위(Character) 데이터로 변환시키는 중개자 역할을 한다고 보면 된다.

(바이트 데이터를 문자 단위로 변환하는 것은, Charset이라는 클래스에서 담당한다. 이 클래스 덕분에 UTF-8 에서 한글이 3Byte여도 2Byte로 변환되어 자바 메모리 상에서 char 타입에 한글이 대입된다.)

우리가 Scanner로 입력받을 때, 한글을 String이나 char 에 저장하고 출력할 때 한글이 깨지는 경우가 없다. 이는 Scanner의 생성자 사진에 보듯이 InputStream으로 키보드 입력을 받아들인다 하더라도, 중개자 역할을 하는 InputStreamReader가 온전한 문자형태로 변환, 처리 해준다.

그래서 이러한 이유로 InputStreamReader를 문자스트림이라고 한다.

다시한번 요약 클릭

자바는 내부적으로 UTF-16을 쓴다. 우리가 EUC-KR, UTF-8 중 어느 것을 써도 charset을 set을 통해 UTF-16으로 변환되어 메모리에 올라가는 것. 또한 UTF-8 인코딩은 유니코드 한 문자를 나타내기 위해 1바이트에서 4바이트까지 사용한다. 그래서 한글인 경우 3Byte를 쓰게 되는데 자바에서 char =’가’; 이렇게 해도 상관없는 이유가 UTF-16은 기본적으로 16bit,즉 내부에서 2byte로 변환되어 올라가기 때문이다.


자 그럼, InputStream이 문자를 그대로 읽지 못하는 경우가 발생하므로, 좀 더 효율적 입력을 위해 중개자 역할인 InputStreamReader를 쓴다.



public class Main {

    public static void main(String[] args) throws IOException {

        InputStream input = System.in;

        InputStreamReader sr = new InputStreamReader(input);


    }

}



테스트를 해보자.



public class Main {

    public static void main(String[] args) throws IOException {

        InputStream input = System.in;

        InputStreamReader sr = new InputStreamReader(input);

        int c = sr.read();

        System.out.println((char)c);
        System.out.println(c);







    }

}





44032는 자바 내부적으로 처리된 UTF-16의 16진수 AC00이다.

InputStreamReader자체가 character(문자) 데이터로 변환하여 메모리에 UTF-16 포맷으로 올라가므로 위의 값을 갖는다.


문자열을 받을 땐 아래와 같이 하면 된다.



public class Main {

    public static void main(String[] args) throws IOException {

        InputStream input = System.in;

        InputStreamReader sr = new InputStreamReader(input);

        char c[] = new char[10];
        sr.read(c);

        for(char val : c) {
            System.out.print(val);
        }    

    }

}





바이트스트림인 InputStream을 통해 입력을 받으면, 문자스트림인 InputStreamReader를 통해 바이트 단위 데이터를 문자(Character) 데이터로 처리할 수 있게 만들어 주는 것이다.

그리고 앞에서 보았듯이 Scanner 를 생성할 때, 문자스트림으로 변환시켜주는 것도 알 수 있다:)

한번 더 정리!

  1. 바이트 단위 데이터를 문자(character) 단위 데이터로 처리할 수 있도록 변환해준다.

  2. char 배열로 데이터를 받을 수 있다




그럼 InputStreamReader를 이해하였으니 다시 Scanner를 들여다보면 ,

우리가 실질적 입력을 받은 방법을 보겠다.

그리고 흔히 우리가 next(), nextInt(), nextDouble, nextFloat() 등 입력을 통한 메소드는 다음과 같이 구성되어있다.

여기선 예시로 nextInt()메소드를 본다.





일단 Scannet.nextInt()를 쓰면 nextInt() 메소드에서 오버로딩된 아래의 nextInt(int radix) 메소드로 보내진다.

일단 우리가 중요하게 봐야할 것은 try-catch문의 String s = next(integerPattern());이다.





이 메소드로 가면 다음과 같이 구성되어 있다.



그러면 또 다른 메소드로 우리를 보내는데, integerPattern()메소드로 가면 기본적으로 입력받기 직전에는 초기화 된 값이 null 값이기 때문에 if 문이 true가 되어 해당 조건문을 실행시킨다.

그리고 여기서 중요한 것이 바로 buildIntegerPatternString() 메서드이다.





여기서 우리는 입력받은 문자를 해당 메서드로 보내서 다음의 정규식을 검사하고 검사 된 문자열을 반환한다.

그래서 Scanner는 속도가 느리다라고 하는 것과 성능이 좋지 않다는 것은 여기에 있다.

다음과 같은 불필요한 정규식을 과도하게 검사하기 때문이다. (물론 장점도 있다. 강력한 정규식 검사로 인해 여러 예외적 입력 값에 대해서도 입력받은 값이 특정 타입으로 변환할 수 있는지를 정확하게 파악할 수 있다. 즉 타입 변환의 안정성이 매우 뛰어나다는 것이다. -> 양날의 검!!)

이렇게 많은 정규식을 통과하여 return 된 정규식의 문자열을 patternCache.forName()으로 보낸다.

이 메소드를 통해 Pattern이라는 타입으로 Compile을 호출하여 String 정규식 문자열을 Pattern이라는 객체로 반환시켜준다. 참고로 Pattern은 java.util.regex 패키지에 있는 클래스이며 정규식의 컴파일 된 표현이다.





그렇게 Pattern 이라는 객체가 반환되면 Pattern integerPattern()에 저장되고 이를 반환시킨다.





그러고 우리가 처음 본 nextInt()로 돌아간다.





Pattern 객체를 String으로 변환시키고 최종적 리턴값은 Integer.parseInt(s,radix)로 인해 int 형으로 반환된다.



다시 정리해보면,

Scanner의 nextInt() 과정은 이러하다.

  1. InputStream으로 입력받는다. 이 때, InputStream 은 바이트스트림

  2. 문자로 온전히 받아들이기 위해 중개자 역할인 InputStreamReader(문자스트림)을 통해 char 타입으로 데이터 처리

  3. 입력받은 문자는 입력 메소드(next(),nextInt(),nextDouble() …) 의 타입에 맞게 정규식 검사

  4. 정규식 문자열을 Pattern.compile()이라는 메소드를 통해 Pattern 타입으로 변환

  5. 반환된 Pattern 타입을 String 으로 변환

  6. String은 입력 메소드의 타입에 맞게 반환시킨다. (Integer.ParseInt() ….)






BufferedReader과 그리고 InputStreamReader(System.in)



끝이 보인다!!

System.in은 바이트스트림인 InputStream 타입이고 이 방법으론 문자를 온전히 전달하기 힘드니 InputStreamReader로 감싸주며 바이트 단위 데이터를 문자 단위로 처리 할 수 있도록 해준다고 하였다.

BufferedReader도 원리가 이와 같다.

BufferedReader를 생성해보자.

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

위 코드를 풀어서 써보자

InputStream is = System.in;
InputStreamReader ir = new InputStreamReader(is);
BufferedReader br = new BufferedReader(ir);

이로써,

  1. 기본적으로 바이트 스트림인 InputStream을 통해 바이트 단위로 데이터 입력을 받는다.

  2. 입력 데이터 char 형태로 처리하기 위해 중개자 역할 문자스트림 InputStreamReader로 감싼다.

근데 InputStreamReader는 어디까지나, ‘문자’만 처리해준다. 문자열처리가 아니다.

(그래서 Scanner 내부에서도 임시 배열을 두어 문자열처럼 사용한다.)

InputStreamReader로 char type으로 처리할 수 있는 장점이 있지만 ..

문자열을 입력할 때에느 매번 배열을 선언해야 한다는 단점이 있다. 그리고, 입력받을 문자열이 길이가 가변적이라면 이는 더욱 큰 단점이다.

그래서 쓰는 것이 Buffer(버퍼)를 통해 문자를 쌓고 한번에 보내는 것.

BufferedReader를 쓸 때 우리는 메소드로 readLine()을 쓴다. 이 메소드는 한 줄을 읽기 때문에 char 배열을 하나하나 생성할 필요 없이 String으로 리턴하여 받을 수 있다는 장점이 있다.

  • Byte Type = InputStream

  • Char Type = InputStreamReader

  • Char Type의 직렬화 (BufferedReader)

    (직렬화란 말이 어렵다면 String이라고 생각해도 무관하다.)

BufferedReader br = new BufferedReader(new InputStreamReader(System.in);

System.in - > InputStream -> InputStreamReader -> BufferedReader

byte 타입을 읽어들이는 in을 char 타입으로 처리한 뒤 String(문자열)로 저장한다.

그렇기 때문에 BufferedReader를 쓸 때 위와 같이 코드가 길어진다.

BufferedReader의 특징은 이러하다

1.버퍼가 있는 스트림이다

2.별다른 정규식을 검사하지 않는다.

위와 같이 두 개의 특징 때문에 Scanner에 비해 성능이 우수하다.

BufferedReader의 경우 Scanner와 다르게 문자열 그대로 읽으므로 별다른 정규식을 하지 않는다.

그렇기 때문에 Scanner에 비해 성능이 좋은 것이다.

스트림은 아래의 구성과 같다.





버퍼는 위에는 용량이 작지만, 실질적으로는 8192개의 문자를 기본적으로 저장할 수 있다.

기본적으로 개행이 되거나, 버퍼가 꽉 차면 버퍼를 비우면서 프로그램으로 데이터를 보낸다.

하나하나 문자를 보내는 것이 아닌, 한 번에 모아둔 다음 보내니 훨씬 속도가 빠르고 정규식 검사를 하지 않으니 속도가 매우 빠른 것이다.

여기서 한번 더 정리,

바이트 단위 InputStream을 문자로 입력받아 Character 처리를 InputStream으로 한뒤 버퍼 BufferedReader 에 담아두었다가 일정 조건이 되면 버퍼를 비우면서 데이터를 보낸다.






마무리



  1. InputStream은 바이트 단위로 데이터를 처리한다. 이 때, System.in이 InputStream 타입이다.

  2. InputStreamReader는 문자(Character) 단위로 데이터를 처리할 수 있도록 한다. InputStream의 바이트 데이터를 문자로 변환해준다.

  3. BufferedReader는 스트림에 버퍼를 두어 문자를 버퍼에 일정 정도 저장해 준 뒤 내보낸다.









자바로 알고리즘을 푸는 경우 Scanner을 사용하는 일은 거의 없다. input에서의 예외적 입력이 거의 없기 때문이다.

그래서 복잡한 정규식 과정을 거칠 필요 없이 BufferedReader을 통해 문자열을 받아오고, Integer.parseInt()같은 파싱 함수를 통해 타입 변환해주는 것보다 성능 과 시간 경쟁에서 효율적인 것이다.

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

댓글남기기