System.in 그리고 InputStream

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

System 클래스의 in 이라는 필드는 InputStream 의 정적 필드

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

import java.io.IOException;
import java.io.InputStream;
 
public class Input_Test {
 
	public static void main(String[] args) throws IOException {
		
		InputStream inputstream = System.in;
		int a = inputstream.read();
		System.out.println(a);
	
	}
}
기본적으로 입출력 클래스는 java.io 라는 패키지에 있다. 그리고 반드시 예외처리를 해주어야한다.
try-catch 문으로 예외를 처리해주어도 되고 위 처럼 메소드에 throws IOException 으로 처리해줘도 된다.
Scanner 나 System.out.print 메소드의 경우 해당 메소드 안에서 자체적으로 예외처리를 하기 때문에 예외처리를 해줄 필요가 없었지만 기본적으로 io 패키지는 IOException 이라는 예외를 던지기 때문에 이를 처리해주어야 정상적으로 컴파일된다.
9
57
923
57

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

 

만약에 나머지 1Byte 도 읽고싶다면 read() 메소드를 두 번 써야된다.

 

import java.io.IOException;
import java.io.InputStream;
 
public class Input_Test {
 
	public static void main(String[] args) throws IOException {
		
		InputStream inputstream = System.in;
        
		byte[] a = new byte[10];
		inputstream.read(a);
 
		for(byte val : a){
			System.out.println(val);
		}
	
	}
}

 byte[] 배열 말고는 다른 타입(int, char 등등..)은 read 메소드에 넣을 수 없다. 당연히 바이트 단위로 읽음

 

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

 

UTF-8 의 경우 한글은 3Byte 를 사용한다.

UTF-8 인코딩 테이블

 3Byte 로 구성되어있고 '가' 라는 문자는 각 1Byte 씩  234, 176, 128 의 구성으로 합쳐져 '가'라고 표현된다는 것을 볼 수 있다.

즉 InputStream.read() 는 1 Byte 밖에 못 읽기 때문에 가장 앞의 1 Byte인 234 까지만 읽고 나머지는 바이트스트림에 남아있게 되는 것이다.

(옆에 있는 UNICODE 가 유니코드에서 코드 포인트(code point)라고 하는 값이다. 즉, '가'라는 문자가 유니코드에서는 16진수로 AC00 이라는 의미다.)

 

그리고 자바는 UTF-16 을 쓴다고 했다. 

234 를 16진수로 변환하면 EA 이고 자바의 인코딩 방식에 따라 0xc3 0xaa 로 변환되어 이에 대응되는 유니코드 테이블의 문자는 다음과 같다.

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

 

 

대부분의 윈도우 사용자는 MS949 가 디폴트로 설정되어있다. 그래서 아까 사진에서 봤듯이 같은 '가' 를 입력하더라도 '째'가 출력되어 나온다.

 

그럼 과정을 한 번 보자.

 

일단 MS949 에서 '가' 라는 문자는 다음과 같다.

즉 16진수로 0xB0A1 이다.

 

B0A1 은 십진수로 176 161 이고 아까 언급했듯이 read 메소드는 1byte 만 읽기 때문에, 즉 B0 만 읽기 때문에 176 만 남게된다.

자바에서는 UTF-16 으로 변환되어 저장된다고 했으니 유니코드의 코드 포인트에서 B0 에 대응되는 값은 무엇일까?

U+00B0 의 값에 대응하여 인코딩 되는 값은 0xc2 0xb0, 즉 11000010 10110000 가 메모리에 저장되는 것이다.

 

그리고 출력한다고 하면 11000010 10110000 에 대응되는, 즉 0xc2 0xb0 에 대응하는 값을 MS949 코드표에서 0xc2b0 를 찾아보면 된다.

 

MS949 코드 테이블을 보면 다음과 같다.

0xC2B0 는 '째'라는 문자다.

 

 

 

Scanner(System.in) 그리고 InputStreamReader(System.in)

import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner; 

public class Input_Test {
 
	public static void main(String[] args) throws IOException {
		
		InputStream inputStream = System.in;
        Scanner scan = new Scanner(inputStream);
        
		int a = scan.nextInt();
		System.out.println(a);
	
	}
}

canner 클래스의 파일에는 Scanner 라는 생성자가 오버로딩(Overloading) 되어있는 걸 볼 수 있다.

 

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

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

 

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

 

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

InputStream 은 우리가 InputStream.read() 를 통해 입력을 받으려고 해도 1Byte 만 인식하니 한글은 입력해봤자 읽지도 못하고 엉뚱한 문자만 나온다. 이를 해결하기 위해 우리가 필요한 건 '문자를 온전하게 읽어들이기' 이다. 그리고 이를 위해 확장시킨 것이 InputStreamReader다. 

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

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

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
 
public class Input_Test {
	public static void main(String[] args) throws IOException {
		
		InputStream inputstream = System.in;
        
		InputStreamReader sr = new InputStreamReader(inputstream);
				
	}
}

더 간단하게 요약하면 아래와 같이도 쓸 수 있다.

InputStreamReader sr = new InputStreamReader(System.in);
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
 
public class Input_Test {
	public static void main(String[] args) throws IOException {
		
		InputStream inputstream = System.in;
		InputStreamReader sr = new InputStreamReader(inputstream);
			
		int c = sr.read();
		
		System.out.println((char)c);
		System.out.println(c);
				
	}
}

그동안 Scanner의 생성자와 메소드 nextInt() 의 과정을 보면 아래와 같이 해석할 수 있다.

  1. InputStream (바이트스트림) 을 통해 입력 받음
  2. 문자로 온전하게 받기 위해 중개자 역할을 하는 InputStreamReader(문자스트림) 을 통해 char 타입으로 데이터를 처리함
  3. 입력받은 문자는 입력 메소드( next(), nextInt() 등등.. ) 의 타입에 맞게 정규식을 검사함
  4. 정규식 문자열을 Pattern.compile() 이라는 메소드를 통해 Pattern 타입으로 변환함
  5. 반환된 Pattern 타입을 String으로 변환함
  6. String 은 입력 메소드의 타입에 맞게 반환함 ( nextInt() - Integer.parseInt() / nextDouble() - Double.parseDouble() 등등.. )

 

BufferedReader 그리고 InputStreamReader(System.in)

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

위 코드를 풀어쓰면 다음과 같을 것이다.

InputStream inputstream = System.in;
InputStreamReader sr = new InputStreamReader(inputstream);
BufferedReader br = new BufferedReader(sr);

Scanner 에서 InputStreamReader 을 설명할 때 '문자'를 처리한다고 했다. '문자열'이 아니다.

Buffer(버퍼)를 통해 입력받은 문자를 쌓아둔 뒤 한 번에 문자열처럼 보내버리는 것이다.

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

 

Byte Type = InputStream

Char Type = InputStreamReader

Char Type 의 직렬화 = BufferedReader

 

즉 하나하나 문자를 보내는 것이 아닌 한 번에 모아둔 다음 보내니 훨씬 속도가 빠르고 별다른 정규식을 검사하지 않으니 더더욱 속도는 빠를 수밖에 없다.

 

즉, 정리하자면 바이트 단위 [InputStream]로 문자를 입력받아 문자(character) [InputStreamReader]로 처리한 뒤 버퍼(buffer) [BufferedReader]에 담아두었다가 일정 조건이 되면 버퍼를 비우면서 데이터를 보내는 것이다.

'JAVA' 카테고리의 다른 글

[JAVA] Excel - Apache POI(SXSSFWorkbook Example)  (0) 2023.05.19
JAVA - 정규표현식(regex)  (0) 2022.11.25
JAVA - 4-12_임의의 정수 만들기  (0) 2022.11.14
JAVA - 4-10_switch문의 제약 조건  (0) 2022.11.14
JAVA - 3-10_산술 변환  (0) 2022.11.14
복사했습니다!