본문 바로가기

JAVA

자바 NIO

1. 자바 NIO란?


JDK 1.4 부터 기존에 느린속도의 IO를 개선하고자 NIO(New IO)가 추가되었다.

 

구분 IO NIO
입출력 방식 Stream 방식 Channel 방식
버퍼 방식 non-Buffer 방식 Buffer 방식
비동기 방식 지원 x 지원 o
블로킹/논블로킹 방식 블로킹 방식 블로킹/논블로킹

 

  • Stream 방식 vs Buffer 방식

Java IO 와 NIO 의 가장 큰 차이점은 IO는 Stream 기반으로 동작하고, NIO는 Buffer 기반으로 동작한다는 것이다.

 

스트림 기반의 Java IO는 스트림으로 부터 한번에 여러 바이트를 읽는다. 따라서 데이터를 읽거나 출력할때 입력 스트림, 출력 스트림을 생성해야한다.

 

버퍼 기반의 java NIO는 스트림과는 다르게 채널을 사용하여 양방향 입출력이 가능하다. 그렇기 떄문에 입출력을 위한 별도의 채널을 생성할 필요가 없는것이다.

 

  • Non-Buffer 방식 vs Buffer 방식

java IO는 Buffer를 사용하지 않기 때문에 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽는다.

이러한 방식은 속도가 느리다

 

java NIO는 Buffer를 사용하여 채널에서 버퍼에 저장된 데이터를 출력하고 입력된 데이터를 버퍼에 저장한다.

따라서 데이터의 위치를 이동해 가면서 필요한 부분만 읽고 쓰는것이 가능하다.

 

  • Blocking 과 Non-Blocking

java IO는 입력 스트림의 read()를 호출하면 데이터가 입력되기 전까지 쓰레드는 Blocking(대기상태)되고

출력 스트림의 write()를 호출하면 데이터 출력 전까지 쓰레드는 블로킹된다. 

IO 쓰레드가 블로킹 되는것은 다른 작업을 할수 없고 블로킹을 빠져나오기 위해 인터럽트 할 수도 없다.

 

java NIO는 Blocking과  Non-Blocking 특징을 모두 가지고있어 NIO Blocking은 쓰레드를 인터럽트함으로써 빠져나올 수 있다.

NIO의 Non-Blocking은 입출력 작업 준비가 완료된 채널만 선택해서 작업 쓰레드가 처리하므로 작업 쓰레드가 블로킹되지 않는다.

 

  • Selector

java NIO Selector는 하나의 thread가 여러개의 input channel을 모니터링 하는것이 가능하다.

이러한 메커니즘으로 단일 쓰레드에서 여러 채널을 쉽게 관리가 가능하다.

 

2. NIO를 사용해보자


public class NioTest {
    public static void main(String[] args) {
        NioTest nt = new NioTest();
        String path = "C:\\Users\\ksw\\IdeaProjects\\filetest\\nio.txt";
        try {
            nt.writeFile(path,"hello");
            nt.readFile(path);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public void writeFile(String path, String data) throws Exception {
        FileChannel channel = new FileOutputStream(path).getChannel();
        byte[] bytes = data.getBytes();
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        channel.write(buffer);
        channel.close();
    }

    public void readFile(String path) throws Exception {
        FileChannel channel = new FileInputStream(path).getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        buffer.flip();
        while (buffer.hasRemaining()){
            System.out.print((char)buffer.get());
        }

        channel.close();
    }
}

 

실행 화면
저장된 파일

writeFile() 메소드

  1.  FileChannel 객체를 만들기 위해 FileOutputStream 클래스의 getChannel() 메소드를 사용하였다.
  2. 우리가 입력할 데이터 정보를 getBytes() 메소드로 만들고, 만든 바이트 배열을 ByteBuffer 클래스에 wrap()이라는 메소드에 담아 호출하면 ByteBuffer 객체가 생성된다.
  3. FileChannel 클래스의 write() 메소드에 Buffer 객체를 넘겨주어 파일에 입력을 한다.

 

readFile() 메소드

  1. FileChannel 객체를 만들기 위해 FileInputStream 클래스의 getChannel() 메소드를 사용하였다.

  2. ByteBuffer 클래스에 선언되어 있는 allocate()메소드로 buffer 객체를 만들었으며, 매개변수 1024는 데이터가 기본적으로 저장되는 크기이다.

  3. 채널의 read() 메소드에 buffer 객체를 넘기면, 데이터를 이 버퍼에 담기기 시작한다.

  4. filp() 메소드는 buffer에 담겨있는 데이터의 가장 앞으로 이동한다.

  5. hasReaming() 메소드를 사용하여 데이터가 더 남아 있는지를 확인하면서 반복작업을 수행하고, get() 메소드를 호출하면 한 바이트씩 데이터를 읽는 작업을 수행한다.