프로그램간의 데이터를 주고 받는 것을 말한다.
자바에서 어느 한 쪽에서 다른 쪽으로 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데, 이것을 스트림(stream)이라 정의한다.
이름에서도 할 수 있듯이 흐름을 뜻하는데, 한 흐름이기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.
스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며, 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다.
연속된 데이터의 흐름으로 입출력 진행시 다른 작업을 할 수 없는 블로킹(Blocking) 상태가 된다.
바이트 기반 스트림으로는 InputStream
, OutputStream
가 있다.
스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라서 다음과 같은 입출력 스트림이 있다.
입력스트림 | 출력스트림 | 입출력 대상 종류 |
---|---|---|
FileInputStream | FileOutputStream | 파일 |
ByteArrayInputStream | ByteArrayOutputStream | 메모리(byte배열) |
PipedInputStream | PipedOutputStream | 프로세스(프로세스간 통신) |
AudioInputStream | AudioOutputStream | 오디오 장치 |
위 입출력 스트림은 각각 InputStream과 OutputStream의 자손들이며, 각각 읽고 쓰는데 필요한 추상 메서드를 자신에 맞게 구현해놓은 구현체이다.
입출력스트림의 부모 InputStream, OutputStream
입력스트림 | 출력스트림 |
---|---|
abstract int read() | abstract void write(int b) |
int read(byte[] b) | void write(byte[] b) |
int read(byte[] b, int off, int len) | void write(byte[] b, int off, int len) |
read()
와 OutputStream의 write(int b)
는read()
와 write(int b)
를 제외한 나머지 메서드들은 추상메서드가 아니니까 굳이 추상메서드인 read()와 write(int b) 를 구현하지 않아도 이들을 사용하면 될 것이라 생각할 수 있겠지만,read()
와 write(int b)
를 이용해서 구현한 것들임으로 read()와 write(int b)가 구현되어 있지 않으면 이들은 아무런 의미가 없다.스트림의 기능을 보완하기 위해 보조스트림이라는 것이 제공된다. 보조스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없지만, 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.
Buffer를 사용하면 좋은 이유는 단순히 모아서 보낸다고 이점이 있는 것이 아닌 시스템 콜의 횟수가 줄어들었기 때문에 성능상 이점이 생긴다.
즉, OS 레벨에 있는 시스템 콜의 횟수 자체를 줄이기 때문에 성능이 빨라지는 것이다.
입력 | 출력 | 설명 |
---|---|---|
FilterInputStream | FilterOutputStream | 필터를 이용한 입출력 처리 |
BufferedInputStream | BufferedOutputStream | 버퍼를 이용한 입출력 성능향상. |
DataInputStream | DataOutputStream | int, float와 같은 Primitive Type 으로 데이터를 처리하는 기능 |
SequenceInputStream | 없음. | 두개의 스트림을 하나로 연결 |
LineNumberInputStream | 없음. | 읽어온 데이터의 라인번호를 카운트 (jdk 1.1부터 LineNumberReader로 대체) |
ObjectInputStream | ObjectOutputStream | 데이터를 객체단위로 읽고 쓰는데 사용. 주로 파일을 이용하며 객체 직렬화와 관련 |
없음. | PrintStream | 버퍼를 이용하며, 추가적인 print관련 기능(print, printf, println 메서드) |
PushbackInputStream | 없음. | 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능 (unread, push back to buffer) |
바이트기반은 입출력의 단위가 1byte 라는 의미이다.
Java에서는 한 문자를 의미하는 char 형이 1byte가 아니라 2byte이기 때문에 바이트기반의 스트림으로 2byte인 문자를 처리하는데에 어려움이 있다.
문자기반 스트림은 Reader
, Write
기반으로 바이트기반의 입출력 스트림의 단점(1byte → 2byte)을 보완하기 위해 문자기반의 스트림을 제공한다.
바이트 기반 스트림 | 문자 기반 스트림 |
---|---|
FileInputStream | FileReader |
FileOutputStream | FileWriter |
ByteArrayInputStream | CharArrayReader |
ByteArrayOutputStream | CharArrayWriter |
PipedInputStream | PipedReader |
PipedOutputStream | PipedWriter |
StringBufferInputStream(deprecated) | StringReader |
StringBufferOutputStream(deprecated) | StringWriter |
InputStream / Reader
InputStream | Reader |
---|---|
abstract int read() | int read() |
int read(byte[] b) | int read(char[] cbuf) |
int read(byte[] b, int off, int len) | abstract int read(char[] cbuf, int off, int len |
OutputStream / Writer
OutputStream | Writer |
---|---|
abstract void write(int b) | void write(int c) |
void write(byte[] b) | void write(char[] cbuf) |
void write(byte[] b, int off, int len) | abstract void write(char[] cbuf, int off, int len) |
void write(String str) | |
void write(String str, int off, int len) |
기존 IO의 단점을 개선하기 위해 java 4 부터 추가된 패키지이다.
스트림(Stream) 기반 IO.
스트림은 입력 스트림과 출력 스트림으로 구분되어 있기 때문에 데이터를 읽기 위해서는 입력 스트림을 생성해야 하고, 데이터를 출력하기 위해서는 출력 스트림을 생성해야 한다.
채널(Channel) 기반 NIO
채널은 스트림과 달리 양방향으로 입력과 출력이 가능하다.
그렇기 때문에 입력과 출력을 위한 별도의 채널을 만들 필요가 없다.
IO에서는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽는다.
이것보다는 Buffer
를 사용해서 한꺼번에 입력받고 출력하는 것이 성능에 이점을 가지게 된다.
그래서 IO는 버퍼를 제공해주는 보조 스트림인 BufferedInputStream, BufferedOutputStream을 연결해 사용하기도 한다.
NIO는 기본적으로 버퍼를 사용해서 입출력을 하기 때문에 IO보다 높은 성능을 가진다.
IO는 블로킹(Blocking) 가능
입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 Thread는 블로킹(대기상태)가 된다.
마찬가지로 출력 스트림의 write() 메소드를 호출하면 데이터가 출력되기 전까지 Thread는 블로킹된다.
IO Thread가 블로킹되면 다른 일을 할 수 없고 블로킹을 빠져나오기 위해 인터럽트(interrupt)도 할 수 없다.
블로킹을 빠져나오는 유일한 방법은 스트림을 닫는것이다.
NIO는 블로킹과 넌블로킹(non-blocking) 특징을 모두 가진다
NIO 블로킹은 Thread를 인터럽트(interrupt) 함으로써 빠져나올 수 있다.
NIO의 넌블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 Thread가 처리하기 때문에 작업 Thread가 블로킹되지 않는다.
작업준비가 완료되었다는 뜻은 지금 바로 읽고 쓸수 있는 상태를 말한다.
NIO 넌블로킹의 핵심 객체는 멀티플렉서(multiplexor)인 셀릭터(Selector) 이다.
셀렉터는 복수 개의 채널 중에서 준비 완료된 채널을 선택하는 방법을 제공해준다.
NIO에서는 데이터를 입출력하기 위해 항상 버퍼를 사용해야 한다.
버퍼는 읽고 쓰기가 가능한 메모리 배열이다.
Buffer는 저장되는 데이터 타입에 따라 분류될 수 있고,
어떤 메모리를 사용하느냐에 따라 종류가 구분될 수 있다.
데이터 타입에 따른 버퍼
메모리 사용에 따른 버퍼
구분 | 넌다이렉트 버퍼 | 다이렉트 버퍼 |
---|---|---|
사용하는 메모리 공간 | JVM의 힙 메모리 | 운영체제의 메모리 |
버퍼 생성 시간 | 버퍼 생성이 빠르다. | 버퍼 생성이 느리다. |
버퍼의 크기 | 작다 | 크다. (큰 데이터 처리에 유리) |
입출력 성능 | 낮다 | 높다. (입출력이 빈번할 때 유리) |
넌다이렉트 버퍼는 JVM 힙 메모리를 사용하므로 생성 시간이 빠르지만,
다이렉트 버퍼는 운영체제의 메모리를 할당받기 위해 운영체제의 네이티브(native) C함수를 호출해야 하고 여러가지 잡다한 처리를 해야하므로 상대적으로 생성이 느리다.
그렇기 때문에 다이렉트 버퍼는 재사용하는 것이 유리하다.
넌다이렉트 버퍼는 JVM의 제한된 힙 메모리를 사용하므로 버퍼의 크기를 크게 잡을 수 없고,
다이렉트 버퍼는 운영체제가 관리하는 메모리를 사용하므로 운영체제가 허용하는 범위 내에서 대용량 버퍼를 생성시킬 수 있다.
참조