여러분은 System.out.println에 대해서 얼마나 알고계신가요?
오늘 소개드릴 내용은 입출력과 파일에 대한 내용입니다. 궁극적으로는 파일 입출력을 다룰려 했지만 그 전에 입출력에 대한 이해를 돕기위해 우리가 매일 보는 콘솔 입출력을 예시로 입출력 스트림에 대해서도 설명드릴 예정입니다.
System.in과 System.out
System 클래스는 시스템에 대한 여러가지 속성과 설정을 할수 있는 클래스입니다.
해당 클래스 안에 미리 정의된 정적 멤버변수가 존재하는데 이들이 바로 System.in과 System.out입니다.
이들을 표준 입출력 객체라하고 이들을 사용해서 콘솔에 입출력하는거지요.
그들이 가지고 있는 메서드 중 콘솔에 대한 출력을 하는 println 메서드가 존재하는 것이고 이것이 우리가 흔히 사용하는 System.out.println의 정체입니다.
InputStream in = System.in;
OutputStream out = System.out;
System.out.println(in.getClass().getName()); //java.io.BufferedInputStream
System.out.println(out.getClass().getName()); //java.io.PrintStream
이들의 타입을 출력하면 BufferedInputStream과 PrintStream이 출력됩니다.
이들은 무엇이고 어떻게 입출력을 하는걸까요?
메인스트림과 보조스트림
자바에서 입출력은 보통 스트림을 통해 진행됩니다. 물론 스트림을 개선시킨 채널이란 것도 존재하지만 오늘은 다루지 않기로 합니다.
스트림은 데이터를 읽고 쓰기 위한 통로이며 이러한 입력과 출력은 동시에 진행될 수 없습니다.
위에서 System.in/System.out을 InputStream/OutputStream 정의한 것이 보일겁니다.
이들은 입출력을 통해 데이터를 읽고 쓰는 최상위 추상 클래스이며 이들과 같이 데이터를 읽는 스트림을 메인스트림이라고 합니다.
또한 이들의 성능을 향상시켜준다거나 출력에 용이한 기능을 부여할수 있게끔하는 스트림들을 보조스트림이라고 합니다. 위에서 BufferedInputStream과 PrintStream이 나왔지요? 이들 InputStream/OutputStream을 확장한 보조스트림입니다.
콘솔 입출력
자 그러면 이들을 통해 미리 정의된 콘솔 입출력을 해봅시다. 해당 소스는 콘솔에 입력을 하면 입력한 내용을 다시 콘솔에 출력하는 프로그램입니다.
public static void main(String[] args) throws IOException { //예외처리는 다음 포스팅에서...
InputStream in = System.in; //입력 객체(BufferedInputStream)
OutputStream out = System.out; //출력 객체(PrintStream)
byte[] readData = new byte[1024]; //데이터를 저장할 변수
int readCount = readData.length;
//콘솔 종료는 ctrl+z 입니다.
while((readCount = in.read(readData)) != -1) { //EOF될 때까지 읽음
out.write(readData, 0, readCount); //읽은 데이터를 출력
}
//자원을 해제합니다.
in.close();
out.close();
}
기본적으로 InputStream/OutputStream은 바이트 단위로 데이터를 읽고 씁니다.
따라서 데이터를 저장할 공간 역시 byte로 선언되야하며 읽을 크기만큼 배열의 길이를 주며 보통은 1024byte 정도를 줍니다. 이 바이트배열을 InputStream.read 메서드의 파라미터로 주면 읽은 데이터가 바이트배열에 저장되며 읽은 크기값을 int형으로 반환합니다.
이때 탈출조건이 -1인 이유는 실제 데이터 -1을 말하는 것이 아니고 EOF를 의미합니다.
EOF란 데이터의 끝을 표현하기 위한 상수로 더 이상 읽을 바이트배열이 존재하지 않는 경우 -1을 반환합니다.
따라서 데이터가 없을 때까지 1024바이트씩 읽는 것입니다.
그리고 저장된 데이터는 OutputStream.write를 사용하여 출력합니다. 이때 파라미터로 출력할 데이터와 그 길이(0부터 바이트배열의 길이)를 지정합니다.
그리고 마지막으로 자원에 대한 연결해제을 close 메서드를 통해 해제합니다.
특히 콘솔에 데이터를 넣을 때는 엔터키를 눌러야 데이터가 들어갑니다. 따라서 데이터가 저장된 바이트배열에는 엔터키가 그대로 들어갑니다. 마찬가지로 데이터를 출력할 때도 이 엔터키가 입력되어 나온다는 점을 인지하고 계셔야합니다.
보조스트림을 사용한 콘솔 입출력
이제는 보조스트림을 사용한 예시를 봅시다.
public static void main(String[] args) throws IOException { //예외처리는 다음 포스팅에서...
InputStream in = System.in;
PrintStream out = System.out; //PrintStream으로 선언
byte[] readData = new byte[1024];
int readCount = readData.length;
while((readCount = in.read(readData)) != -1) {
out.print("읽은 데이터는 " + new String(readData, 0, readCount)); //PrintStream의 print 사용
}
in.close();
out.close();
}
PrintStream은 보조스트림으로 출력에 대해 편의성을 제공하는 메서드 println, print 등을 제공합니다.
이것이 우리가 지겹도록 보았던 System.out.println의 정체입니다!
예시에서는 OutputStream 대신 PrintStream을 선언하여 println, print 를 메서드를 사용할 수 있게 만들었습니다.
여기서 드는 의문점 한가지...그렇다면 우리가 자주 쓰던 Scanner Class는 보조스트림일까요?
Scanner Class는 보조스트림?
결론부터 말하자면 Scanner는 보조스트림이 아닙니다. 하지만 마치 쓰임새는 보조스트림같이 사용되는데요.
Scanner는 데이터를 읽는 것이 아닌 공백으로 구분된 토큰을 통해 구문 분석하여 값을 가져오는 것 뿐입니다.
이에 따라서 입출력에 대한 예외처리 또한 할 필요가 없죠.
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("읽은 데이터는 " + sc.nextLine());
sc.close();
}
그렇지만 Scanner는 대부분의 상황에서 입출력 스트림보다 속도가 느린 점 주의하셔야 합니다.
참고
https://www.techguruspeaks.com/java-standard-streams/
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
'Java > Grammer' 카테고리의 다른 글
Stream API 정리 (0) | 2022.06.04 |
---|---|
입출력 스트림 (2) - File I/O (0) | 2022.05.24 |
ShallowCopy와 DeepCopy 완전 정복 (0) | 2022.05.07 |
객체의 중복/정렬 그리고 Collection (0) | 2022.04.24 |
Simple Data Structure (2) - List/Set/Map (0) | 2022.04.23 |