본문 바로가기

Java

NIO 제대로 파해쳐보자

Java NIO Overview



Java NIO ( New I/O )는 Java 1.4부터 사용하던 IO API의 대안책(Java IO, Java networking API)이다. Java NIO는 기존의 IO와 다른 방식으로 동작을 제안한다. 


1) Java NIO : Non-blocking IO

 예를 들면, Thread가 channel 에게 data를 읽어 buffer로 넣도록 요청할 수 있다. channel이 데이터를 읽어 buffer에 넣는 동안, 스레드는 다른 일을 할 수 있다. data를 읽어 buffer에 다 넣으면, 스레드는 이제 작업을 진행하면 된다. channel에 쓰는 것도 똑같은 방식으로 진행된다.


2) Java NIO : Selectors

 Java NIO는 Selector라는 개념을 가지고 있다. selector는 connection opened, data arrived등과 같은 이벤트를 위한 채널들을 모니터해주는 객체이다. 즉, 단일 스레드에서 데이터를 위한 채널들을 모니터할 수 있다.


3) Java NIO : Channel과 Buffer

 기존 IO API에서는 byte stream과 character stream을 이용했다. NIO API에서는 channel과 buffer를 이용한다. Data는 항상 channel에서 buffer로 읽히고, buffer에서 channle로 쓰여진다.


Java NIO core components

  • Channels

  • Buffers

  • Selectors


Channels과 Buffers



전형적으로, NIO의 모든 IO는 Channel로부터 시작된다. 


Java NIO에서 Channel 구현체 :

  • FileChannel

  • DatagramChannel

  • SocketChannel

  • ServerSocketChannel

위를 보면, file IO에서 뿐만 아니라 UDP + TCP Network IO도 커버를 하고 있다.

Java NIO에서 Buffer 구현체 :
  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

Selectors



Selector는 단일 스레드에서 여러개의 channel을 다룰 수 있도록 해준다. 만얀 어플리케이션이 많은 connection(Channel)이 열려 있을 때 간편하고, 각자의 connection에는 낮은 traffic을 가진다. 예를 들면, chat server와 같이. 

Selector를 쓰기 위해서는 Channel을 등록해야 한다. 그러면 select() method를 호출하면 된다. 이 함수는 등록된 채널 중 하나가 EVENT가 준비될 때 까지 block한다. 이 함수가 return 되면, 그 스레드는 해당 event를 진행한다. Event 종류에는 데이터와 관련된 것이 있다. 



Java NIO AsynchronousFileChannel



 Java 7에서 AsynchronousFileChannel이 Java NIO에 추가되었다. 해당 클래스는 file 읽고 쓰기를 비동기로 할 수 있게 해준다. 어떻게 사용하는지에 대해 알아보자.


1) AsynchronousFileChannel 만들기


static 함수인 open()으로 채널을 만들자. 

Path path = Paths.get("data/test.xml");

AsynchronousFileChannel fileChannel =
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

첫번째는 파일을 가리키는 Path이고, 두번째는 Option에 관련된거 인데, 해당 예제는 file을 읽기용으로만 쓰겠다는 옵션이다.


2) Data 읽기

 

AsynchronousFileChannel으로 부터 data를 읽는 방법은 2가지가 있다.


  • Future 이용
첫번째 방법은 Future를 리턴해주는 read() 함수다.
Future<Integer> operation = fileChannel.read(buffer, 0);

첫번째 parameter는 ByteBuffer이다. 해당 Channel로부터 온 데이터는 ByteBuffer로 읽힌다. 

두번째 parameter는 읽기 시작할 byte position이다.


read 동작이 완료되지 않더라도 read() 함수는 바로 return 된다. read()함수에서 반환된 Future 객체의 isDone() 함수를 이용하여 read 동작이 언제 완료됐는지 체크 할 수 있다.


read() 함수 사용 예제를 살펴보자.


AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

Future<Integer> operation = fileChannel.read(buffer, position);

while(!operation.isDone());

buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();

해당 예제는 CPU를 효과적으로 사용하지는 않았고, 끝날때까지 결국 기다려야 한다.


  • CompletionHandler 이용
read() 함수에 parameter로 CompletionHandler를 넘긴다. 

fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result = " + result);

        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

    }
});

read 동작이 끝나면 CompletionHandler의 completed가 호출된다. result는 몇 바이트가 읽혔는지를 나타낸다.

만약 read 동작이 실패하면 failed()가 호출된다.

http://tutorials.jenkov.com/java-nio/asynchronousfilechannel.html