이제까지 자바의 I/O는 느리다, 라는 인식이 대체적이었다.
 
물론 아래과 같은 자바 클래스와 Buffered 계열의 클래스를 통해 손쉽게 I/O 작업을 할 수는 있었다.
 
  • FileReader
  • PrintReader
  • FileWriter
  • PrintWriter

 

하지만 커널 버퍼를 직접 접근하는 Direct Buffer를 핸들링할 수 없었기 때문에, 커널에서 JVM 내부로 옮겨와야 하는 오버헤드 때문에 느렸건 것이 사실이다. 즉, 좀 더 자세히 보자면 아래와 같은 이유 때문이다.
 
  • JVM 내부로 복사할 때 CPU 사용
  • 복사 Buffer 사용 후 GC를 통해 정리 (GC 대상이 됨)
  • 복사 진행 중에 (즉 I/O 작업 중) 해당 스레드 Blocking
 
할말은 많지만 여기까지만 적겠다.
 
이제 과거의 I/O는 편의상 BIO로 지칭한다.
 
 
NIO는 Nonblocking I/O, 혹은 New I/O의 줄임말이다.
 
이전의 I/O, 즉 Blocking I/O는 모든 처리가 블럭화되어 있었다. 해당 블럭이 끝나기 전에는 아무 것도 수행할 수 없는 것이다. 그래서 JDK 1.4부터 NIO가 생겼다.
 
그 전에는 왜 없었을까 하면, 자바는 Write Once, Run Anywhere다. 그래서 JVM이 실행되는 각 OS의 System Call이나 커널을 이용하는 기술은 어려웠던 것이 사실이다.
 
그러나 시간이 지나서 표준 인터페이스를 제공하고 그 밑단에서 각 OS별로 네이티브하게 처리할 수 있는 기술이 완성된 것이다.
 
위에서 말한 커널의 Direct Buffer, NIO가 되면서 그 커널의 Direct Buffer에 접근할 수 있는 클래스가 생겼다.
 
 
이제 Buffer에 대해 좀 더 디테일하게 알아보자.
 
Buffer는 시스템 메모리를 직접 사용할 수 있는 클래스로 기본 데이터 타입을 저장할 수 있다. 형태는 배열과 유사하여 제한된 크기에 순서대로 데이터를 저장한다.
 
Buffer 자체는 추상 클래스이며 8개의 Buffer를 제공하는데 그 종류는 다음과 같다.
 
  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
 
 
각 Buffer에는 여러 속성이 있는데 다음과 같다.
 
  • position : Buffer에서 현재 read하거나 write할 위치. 초기값은 0이며 limit을 초과할 수 없다
  • limit : Buffer에서 read 혹은 write할 수 있는 최대 값. 초기값은 capacity이며 capacity를 초과할 수 없다.
  • capacity : Buffer의 크기다. 한번 생성되면 크기를 바꿀 수 없다.
  • mark : 현재 position을 표시할 때 사용한다. reset() 메소드로 mark 위치로 position을 변경할 수 있다.
 
 
Buffer 클래스의 메소드에는 clear(), rewind(), flip(), compact(), duplicate(), slice() 등이 있다.
 
앞서 Direct Buffer에 대한 이야기를 했었는데, ByteBuffer는 시스템 메모리를 직접 사용하는 Direct Buffer를 만들 수 있는 유일한 Buffer다.
 
 
그리고 그에 딸린 부가적인 개념도 생겼다.
 
Channel과 Selector이다. 이들을 통해 드디어 System Call을 사용할 수 있게 되었다.
 
BIO는 스트림 기반이다. 스트림은 입력 스트림, 출력 스트림으로 나뉜다. 따라서 뭘 읽기 위해서 입력 스트림, 뭘 출력하기 위해서 출력 스트림을 따로 만들어야 했다.
 
하지만 NIO는 Channel을 통해 양방향으로 read, write를 동시에 할 수 있게 해준다. BIO에서 FileInputStream, FileOutputStream을 다 만들었다면 NIO에서는 FileChannel 하나만 만들면 된다.
 
모든 Channel 클래스는 public 생성자를 제공하지 않는다. 따라서 Channel을 생성하기 위해 기존 스트림, 소켓, 서버소켓 클래스의 getChannel() 메소드를 사용한다.
 
SocketChannel, ServerSocketChannel은 open()이라는 static 메소드를 제공한다.
 
 
 
Selector는 모든 연결을 중앙집중식으로 관리하는 것이다. 이 Selector만 Blocking된다.
 
예를 들어 특정 클라이언트가 서버와 통신을 한다, 그러면 Selector가 이를 감지하여 이벤트가 발생한 I/O와 관련된 Channel을 불러와서 작업을 수행하는 것이다.
 
 
자바 7에서는 NIO2가 등장했는데, 기존 NIO 패키지(java.nio.channels, java.nio.charset, java.nio.file)에 포함되어 있다.
 
NIO2는 크게 파일시스템 API와 비동기채널로 구분된다.
 
 
정리를 해보자.
 
* BIO와 NIO 비교
입출력 Stream Channel
버퍼 Non-Buffer Buffer
비동기 지원 X O
Blocking Blocking only Blocking / Non-Blocking