막연하게만 사용하던 I/O를 조금 더 이해해보고자 작성한 글입니다.
기본적으로 I/O는 O/S 레벨에서 이루어지고 byte를 다룬다는 것을 생각야해합니다.
byte를 다루는 InputStream과 달리 Reader 클래스는 String을 다룰 때 사용되는 클래스이다.
Reader클래스들은 InputStream을 내부에서 사용하거나 랩핑하여 사용한다.
Reader = InputStream + String 핸들링
영문을 입력할 때와 한글을 읽을 때를 각각 살펴보자.
// test.txt
this is a test sentence
한글 테스트 입니다.
public static void main(String[] args) throws IOException {
FileInputStream fileStream = new FileInputStream("test.txt");
int data = fileStream.read();
System.out.println("stream: " + (char) data);
FileReader fileReader = new FileReader("test.txt");
int res = fileReader.read();
System.out.println("reader: " + (char) res);
}
정확히는 ASCII 문자셋에 포함되는 글자를 입력하는 경우이다.
이때는 두 코드 결과값의 차이가 없다. 왜냐하면 1byte를 읽어도 완성된 문자이고, 1글자를 읽어도 1byte이기 때문이다.
// text가 "this is a test sentence" 일 경우
System.out.println("stream: " + (char) data); // t
System.out.println("reader: " + (char) result); // t
정확히는 유니코드에서 1byte이상으로 표현되는 문자의 경우이다.
InputStream의 경우는 인코딩결과와 상관없이 1byte를 읽기 때문에 입력한 글자와 다른 글자가 표현되지만, FileReader의 경우는 글자 단위로 읽기 때문에 인코딩을 감안한 byte를 읽는다.
// text가 "한글 테스트 입니다." 일 경우
System.out.println("stream: " + (char) data); // í
System.out.println("reader: " + (char) result); // 한
다른 Reader클래스를 사용하기 위해 인터페이스 변환이 필요한 경우와 특정 인코딩을 사용하기 위한 경우 2가지에 사용될 수 있다.
이전 글에서 작성한 소켓 통신 서버 코드이다. String을 다루는 경우 BufferedReader를 사용하면 훨씬 손쉽게 InputStream을 핸들링할 수 있다.
이 때, BufferedReader는 생성시에 Reader 타입을 인자로 받기 때문에 InputStream을 InputStreamReader 타입으로 변경이 필요하다.
public static void main(String[] args) throws IOException {
ServerSocket serverSock = new ServerSocket(port);
while(true) {
Socket socket = serverSock.accept();
InputStream in = socket.getInputStream();
Reader reader = new InputStreamReader(in); // 인터페이스 변경
BufferedReader bufReader = new BufferedReader(reader);
String msg = "";
while((msg = bufReader.readLine()) != null) {
System.out.println(msg);
}
}
기본적으로 Reader는 JVM에 설정되있는 encoding방법으로 문자열 처리를 하기 때문에 별도의 encoding 방법을 사용하고 싶다면 InputStreamReader가 필요하다.
public static void main(String[] args) throws IOException {
InputStream in = System.in;
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
int data = reader.read();
System.out.println((char) data);
}
InputStreamReader의 2번째 인자로 전달하는 인코딩 방법에 따라 결과가 달라진다.
기본 매커니즘은 BufferedInputStream과 같지만, 추가적으로 readLine 메소드를 제공한다.
문장 단위로 결과를 반환하고, 더이상 문장이 없을 경우 null을 반환한다.
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader("./src/ttest.txt");
BufferedReader bufReader = new BufferedReader(reader);
String data;
while((data = bufReader.readLine()) != null) {
System.out.println(data);
}
}
public static void main(String[] args) throws IOException {
ServerSocket serverSock = new ServerSocket(port);
while(true) {
Socket socket = serverSock.accept();
InputStream in = socket.getInputStream();
Reader reader = new InputStreamReader(in); // 인터페이스 변경
BufferedReader bufReader = new BufferedReader(reader);
String msg = "";
while((msg = bufReader.readLine()) != null) {
System.out.println(msg);
}
}
String타입을 글자단위의 바이트로 읽는다.
언뜻 toCharrArray로 바꿔서 사용할 수도 있다는 생각이 들지만, reader 타입이기 때문에 다른 stream들과 연계하기 사용하기 좋은 이점이 있다.
public static void main(String[] args) throws IOException {
String msg = "한글이다";
StringReader reader = new StringReader(msg);
int data;
while ((data = reader.read()) != -1) {
System.out.println(data);
}
}
Reader 클래스는 Stream에 String 핸들링 로직을 더해서, String 관련 작업을 편하게 해주는 클래스이다.
BufferedReader를 사용하면 readLine을 사용할 수 있다.