본문 바로가기

프로그래밍/Java

NIO(1)

반응형

NIO(1)

날짜: 2021년 6월 14일

학습목표

자바의신 교재를 통해 NIO에 대해서 간략하게 알아보자.

  • NIO란?
  • NIO 문법 및 실습
  • NIO의 Buffer 클래스

NIO란?

JDK1.4 부터 NIO(New IO)가 추가되었다. NIO가 생긴 이유는 단 하나다. 바로 속도때문이다.

NIO는 스트림을 사용하지 않고, 채널(Channel)과 버퍼(Buffer)를 사용한다.

  • NIO는 왜 쓰나? : IO보다 빠르다.
  • NIO와 IO의 차이점 : IO는 스트림을 쓰지만, NIO는 채널과 버퍼를 사용한다.

NIO를 직접 사용해보자!

NIO를 예제코드를 통해서 직접 사용해보고, 이해해보자.

package nio;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import static java.io.File.separator;

public class NioSample {

    public static void main(String[] args) {
        NioSample sample =  new NioSample();
        sample.basicWriteAndRead();
    }
    public void basicWriteAndRead() {
        String fileName =separator+"nio.txt";
        try {
            writeFile(fileName, "My Fisrt NIO Sample");
            readFile(fileName);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public void writeFile(String fileName, String data) throws Exception {
        FileChannel channel = new FileOutputStream(fileName).getChannel();
        byte[] byteData = data.getBytes();
        ByteBuffer buffer = ByteBuffer.wrap(byteData);
        channel.write(buffer);
        channel.close();
    }
    public void readFile(String fileName)throws Exception {
        FileChannel channel = new FileInputStream(fileName).getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        buffer.flip();
        while(buffer.hasRemaining()) {
            System.out.print((char)buffer.get());
        }
        channel.close();
    }
}
/*
My Fisrt NIO Sample
*/

실행결과 파일도 생성되었고, 또 파일 내용또한 성공적으로 읽어 콘솔에 출력한것을 확인할 수 있다.

그럼 위 예제 코드에서 writeFile(), readFile() 메소드에 대해서 자세히 알아보자.

  • writeFile()

      public void writeFile(String fileName, String data) throws Exception {
          FileChannel channel = new FileOutputStream(fileName).getChannel();
          byte[] byteData = data.getBytes();
          ByteBuffer buffer = ByteBuffer.wrap(byteData);
          channel.write(buffer);
          channel.close();
      }
    1. 파일을 쓰기 위해 FIleChannel 객체를 만들려면, 위와 같이 FileOutputStream 클래스에 선언된 getChannel() 메소드를 호출한다.
    2. ByteBuffer 클래스에 static으로 선언된 wrap() 메소드를 호출하면, ByteBuffer 객체가 생성된다. 이 메소드의 매개 변수는 저장할 byte의 배열을 넘기면 된다.
    3. FileChannel 클래스에 선언된 write() 메소드에 buffer 객체를 넘겨주면 파일에 쓰게된다.
  • readFile()

      public void readFile(String fileName)throws Exception {
          FileChannel channel = new FileInputStream(fileName).getChannel();
          ByteBuffer buffer = ByteBuffer.allocate(1024);
          channel.read(buffer);
          buffer.flip();
          while(buffer.hasRemaining()) {
              System.out.print((char)buffer.get());
          }
          channel.close();
      }
    1. 파일을 읽기 위한 객체 역시 FileInputStream 클래스에 선언된 getChannel() 메소드를 통해 생성한다.
    2. ByteBuffer클래스에 선언된 allocate() 메소드를 통해서 buffer 객체를 만들었따. 여기서 매개변수(1024)는 데이터가 기본적으로 저장되는 크기를 의미한다.
    3. 채널의 read() 메소드에 buffer 객체를 넘겨줌으로써, 데이터를 이 버퍼에다 담으라고 알려준다. 이렇게 알려주기만 하면, buffer에는 데이터가 담기기 시작한다.
    4. flip() 메소드는 buffer에 담겨있는 데이터의 가장 앞으로 이동한다.
    5. get() 메소드를 호출하면 한 바이트씩 데이터를 읽는 작업을 수행한다.
    6. ByteBuffer에 선언된 hasRemaining() 메소드를 사용, 데이터가 더 남아있는지 확인하며 반복작업을 수행한다.

Channel의 경우 간단하게 객체만 생성해 read나 write만 호출하면 된다고 생각하면 된다. 그러나. Buffer클래스는 간단하지 않으므로 좀더 자세히 알아보자.

Channel 은 Stream과 마찬가지로 close()로 닫아줘야 한다!!

Buffer 클래스

NIO에서 제공하는 Buffer는 java.nio.Buffer 클래스를 확장해 사용한다. 예제 코드에서는 ByteBuffe만 사용했지만, 그외에 CharBuffer,DoubleBuffer,IntBuffer 등이 존재한다. 이런 Buffer클래스의 flip() 메소드 외에 대해서 알아보자.

여기서 position(위치)라는 말이 나오는데, 버퍼는 CD처럼 위치가 있다. 버퍼에 데이터를 담거나, 읽는 작업을 수행하면 현재의 '위치'가 이동한다. 그래야 다음 '위치'에 있는 것을 바로 쓰거나 읽을 수 있기 때문이다.

여기서 잠시 Buffer의 위치를 나타내는 변수들의 관계를 기억하고 넘어가자, NIO를 제대로 이해하려면 이 세 변수의 관계를 꼭 이해하고 기억해야만 한다.

0 <= position <= limit <= capacity

그럼 다음 예제코드를 통해서 위의 세 메소드들을 이해해보자.

package nio;

import java.nio.IntBuffer;

public class NioDetailSample {

    public static void main(String[] args) {
        NioDetailSample sample = new NioDetailSample();
        sample.checkBuffer();
    }
    public void checkBuffer() {
        try {
            IntBuffer buffer = IntBuffer.allocate(1024);
            for(int loop=0; loop<100; loop++) {
                buffer.put(loop);
            }
            System.out.println("Buffer capacity = " + buffer.capacity());
            System.out.println("Buffer limit = " + buffer.limit());
            System.out.println("Buffer position = " + buffer.position());
            buffer.flip();
            System.out.println("Buffer fliped!!");
            System.out.println("Buffer limit = " + buffer.limit());
            System.out.println("Buffer position = " + buffer.position());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}
/*
Buffer capacity = 1024
Buffer limit = 1024
Buffer position = 100
Buffer fliped!!
Buffer limit = 100
Buffer position = 0
*/

코드를 분석해보자.

  1. capacity() 메소드의 결과는 쉽게 이해될것이다. 1024로 크기를 지정했으니, 당연하게도 1024가 나온다.
  2. limit의 경우 크기를 별도지정하지 않았으므로, 기본 크기인 1024가 나온다.
  3. for루프문을 통해 데이터가 추가된 후 버퍼의 위치는 100이 나온다.
  4. flip() 메소드는 버퍼의 가장 첫 자리로 이동한다고 했다. 그러므로 flip이후에 position은 0이된다. 여기서 주의할 점은 limit값이 flip이전의 position값으로 지정된다는 점이다.

위 세변수의 관계에 mark라는 변수를 하나 더 추가해서 관계를 기억하자.

0 <= mark <= position <= limit <= capacity

그럼 이제 위치를 변경하는 메소드들에 대해서 알아보자.

언뜻 보면 flip() 과 rewind()가 비슷해보이지만, 앞서 말했다싶이, filp()은 limit값을 변경한다!

그럼 다시 예제코드를 작성하며 해당 메소드들을 직접 사용해보자.

public void checkBufferStatus() {
        try {
            IntBuffer buffer = IntBuffer.allocate(1024);
            buffer.get();
            printBufferStatus("get", buffer);
            buffer.mark();
            printBufferStatus("mark", buffer);
            buffer.get();
            printBufferStatus("get", buffer);
            buffer.reset();
            printBufferStatus("reset", buffer);
            buffer.rewind();
            printBufferStatus("rewind", buffer);
            buffer.clear();
            printBufferStatus("clear", buffer);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    public void printBufferStatus(String job, IntBuffer buffer) {
        System.out.println("Buffer " +job+  " !!!" );
        System.out.format("Buffer position=%d remaining=%d limit=%d\n",buffer.position(), buffer.remaining(), buffer.limit());
    }
/*
Buffer get !!!
Buffer position=1 remaining=1023 limit=1024
Buffer mark !!!
Buffer position=1 remaining=1023 limit=1024
Buffer get !!!
Buffer position=2 remaining=1022 limit=1024
Buffer reset !!!
Buffer position=1 remaining=1023 limit=1024
Buffer rewind !!!
Buffer position=0 remaining=1024 limit=1024
Buffer clear !!!
Buffer position=0 remaining=1024 limit=1024
*/

Feedback

NIO가 왜 탄생했는지, 그리고 기본적인 메소드 사용법들에 대해서 익혔다. 역시나 자세한건 알수없었다. 버퍼의 위치(position)에 대해서 나왔는데, 이 위치가 버퍼에 어떤영향을 미치는지, 또 왜 NIO가 IO보다 빠른건지 다음 포스팅에서 알아보자

알게된 점

  • NIO란?
    • IO보다 더 빠른 속도를 위해 탄생한 입출력 관련 클래스
  • NIO 문법 및 실습
    • Channel 은 close()를 해줘야 한다.
  • NIO의 Buffer 클래스
    • 버퍼는 position(위치) 개념이 있다.
    • 0 ≤ mark ≤ position ≤ limit ≤ capacity 관계를 기억하자.
    • 위치를 변환하는 여러 메소드들이 존재한다.

알아야 할 점

  • 버퍼의 '위치'로 인한 버퍼의 특성
  • NIO는 왜 IO보다 빠른가?

References

자바의 신 VOL2

반응형

'프로그래밍 > Java' 카테고리의 다른 글

NIO(3)  (0) 2021.06.14
NIO(2)  (0) 2021.06.14
Serializable(3)  (0) 2021.06.13
Serializable(2)  (0) 2021.06.13
Serializable(1)  (0) 2021.06.11