본문 바로가기

Programing/JVM(Java, Kotlin)

스트림(Stream)

스트림은 자바에서 입출력에 관계된 추상화된 개념이다.

또한 입출력이란 컴퓨터 장치와 다른 어떤것(사람, 다른 컴퓨터 등)과의 인터페이스를 담당하고 엄청 자주 사용된다.


하지만 자바를 배우는 사람은 의외로 스트림에 대한 개념이 약하다는 것을 느낀다. (예. 김xx, 장xx)

System.out.println이 PrintStream의 일종이라는 것을 아는 사람이 몇명이나 될까 느낀다.


입출력 클래스는 다루는 데이터의 종류에 따라 계층(level)을 가지고 있다.

추상화가 덜된 바이너리 데이터부터 구체화가 된 데이터 타입까지 말이다. (특히 데이터는 문자열이 되면서 다룰 수 있고 없는 데이터가 생기게 된다.)


모든 데이터를 다룰 수 있다 : InputStream / OutputStream

이 InputStream과 OutputStream은 byte단위의 데이터를 다룬다. 즉, 바이너리 데이터를 다룰 수 있다고 보면 된다.

읽고 쓰기 위해 read()와 write()라는 메소드가 제공되며 인자를 보면 byte 형태라는 것을 알 수 있다.


이 두 클래스는 추상 클래스이기에 인스턴스화를 할 수 없다.
따라서 실제로 생성은 구체 클래스를 사용해야 하는데, 아래와 같은 것을 쓸 수 있다.

OutputStream - ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream, OutputStream, PipedOutputStream

InputStream - AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream




문자 기반의 입출력 : ReaderWriter

이제 문자라는 어떤 데이터 형태로 나타난다. 따라서 이제 인코딩이라는 개념이 등장하게 된다.

역시 읽고 쓰기 위해 read()와 write()라는 메소드가 제공되는데 인자는 char 형태이다.


이 추상클래스를 구현한 구체클래스는 아래와 같다.

Reader - BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader

Writer - BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, PrintWriter, StringWriter


인코딩이라는 것은 기본 바이트를 일정한 묶음(chunk)으로 다룬다는 것을 의미한다. 일종의 덩어리 데이터를 다루기 때문에, 버퍼라는 개념을 사용하기 쉬워진다. 일반적으로 I/O는 블록(block)단위로 하는 것이 바이트 하나 하나 가져오는 것보다 효율적이다. 핸드폰도 문자 보낼 때 단문(80bytes)이 허용하는 글자를 채워서 보내는 것이 돈이 적게 들지 않는가?

BufferedReader와 BufferedWriter라는 성능을 극대화 하기 위한 클래스가 있다.

예를 들어 표준 입력(키보드) 읽기를 할 때 그냥 System.in에서 읽는 것 보다.

BufferedReader in

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

와 같이 버퍼로 래핑을 하는 것이 성능이 좋아진다.


기능이 추가된 클래스 들

PrintStream - FilterOutputStream를 확장(상속)한 클래스이다.
 print, printf, println 등의 다양하게 오버로드된 형태로 출력을 할 수 있다.


참고로 java.io 패키지는 아니지만 입력에 유용하게 클래스

Scanner - 간단한 텍스트 스캐너로 기본 데이터 타입들과 정규식을 사용한 문자열을 통해 데이터를 파싱할 수 있다.

Scanner sc = new Scanner(System.in);

Scanner sc = new Scanner(new File("myNumbers"));

Scanner는 문자셋(charset)기반의 입출력이기 때문에 해당 데이터의 인코딩에 주의할 필요가 있다.
파일업로드를 통해서 받은 텍스트 파일(utf8로 인코딩)의 InputStream을 이용해서 라인단위로 읽었는데, 개발시에는 잘 읽히던 것이 막상 deploy를 하고 나니 한글을 읽지 못하는 문제가 있었다. 해결은 Scanner 생성시 두 번째 인자로 charset을 지정해주었다.

 Scanner의 생성시 InputStream으로 생성하면 내부적으로는 InputStreamReader를 이용하게 된다. 문제는 이것이 플랫폼의 기본 문자열집합을 사용한다는 것에 있다. 프로퍼티명은 file.encoding 이다.
 System.getProperty("file.encoding")으로 가져올 수 있다. 참고


-------

1.4부터 추가된 NIO(nexus for I/O)는 성능이 많이 개선되었다고 한다.

채널이라는 개념이 추가 되었다. 패키지는 java.nio.

채널에 가장 기본이 되는 인터페이스는 Channel이다.

이 인터페이스를 상속받은 서브 인터페이스는 아래와 같다.

AsynchronousByteChannel, AsynchronousChannel, ByteChannel, GatheringByteChannel, InterruptibleChannel, MulticastChannel, NetworkChannel, ReadableByteChannel, ScatteringByteChannel, SeekableByteChannel, WritableByteChannel


자바의 NIO와 IO의 주요 차이는 아래와같다. (출처)

IONIO
스트림 기반(Stream)버퍼(Buffer) 기반
블록킹(Blocking) IO논블로킹(Non blocking) IO
 셀렉터(Selectors)

나중에 나온 NIO가 IO보다 성능이 좋다고 인식을 할 수 있으나 knight76님의 블로그의 '자바(java) io와 nio'란 글을 보면 꼭 그렇지만은 않다는 결론을 얻을 수 있다. 잘 써야 한다는 것.