본문 바로가기

# 02/Java

[윤성우 열혈자바] 33-3. NIO 기반의 입출력

반응형

I/O 스트림의 성능을 보완하기 위해 채널과 버퍼가 나옴!

I/O는 왜 느릴까?

구조적인 문제 - 가상머신에서 작동하기 때문

앱에서 자바가상머신을 거쳐 O/S를 거쳐 하드웨어를 거쳐 파일까지 도달

극복하기 위해 가상머신을 거치지 않고 다이렉트로 O/S에 전달 (채널, 버퍼)

I/O에 성능이 문제되지 않는 상황이 훨씬 많기 때문에 무조건 채널과 버퍼를 사용할 필요는 없음!




NIO 채널(Channel) 버퍼(Buffer)


스트림과 채널의 공통점

 스트림도 채널도 데이터의 입력  출력을 위한 통로가 된다."


스트림과 채널의 차이점

 스트림은  방향으로만 데이터가 이동하지만 채널은 양방향으로 데이터 이동이 가능하다."



채널에만 존재하는 제약사항

 채널은 반드시 버퍼에 연결해서 사용해야 한다."

채널과 버퍼는 독립적임. 버퍼와 채널은 일대일 일대다 다대일 다대다 모두 가능!

 

채널 기반 데이터 입출력 경로

   출력 경로: 데이터  버퍼  채널  파일

   입력 경로: 데이터  버퍼  채널  파일








NIO 기반 파일 복사 예제


public static void main(String[] args) {

   Path src = 복사할 대상 파일

   Path dst = 사본 이름 

 

   // 하나의 버퍼 생성 - 인스턴스 생성

   ByteBuffer buf = ByteBuffer.allocate(1024);

   

   // try에서  개의 채널 생성 - 버퍼와 채널은 별개

   try(FileChannel ifc = FileChannel.open(src, StandardOpenOption.READ);

          FileChannel ofc = FileChannel.open(dst, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {

      int num;

      while(true) {

         num = ifc.read(buf);         // 채널 ifc에서 버퍼로 읽어 들임

         if(num == -1)         // 읽어 들인 데이터가 없다면

            break;

     

         buf.flip();         // 버퍼 모드 변환!

         ofc.write(buf);         // 버퍼에서 채널 ofc 데이터 전송

         buf.clear();         // 버퍼 비우기

      }

   }

   catch(IOException e) {

      e.printStackTrace();

   }

}








성능 향상 포인트는 어디에?


효율적인 버퍼링

   기본 IO 스트림 기반의 복사 프로그램과 달리 하나의 버퍼만을 사용하였다.

   버퍼의 수가 줄은 것이 핵심이 아니라, 이로 인해 버퍼에서 버퍼로의 데이터 이동이 불필요해진 부분이 핵심


Non-direct 버퍼를 대신하는 Direct 버퍼의 생성

   ByteBuffer buf = ByteBuffer.allocate(1024);   // Non-direct 버퍼

        : 파일  운영체제 버퍼  가상머신 버퍼  실행 중인 자바 프로그램

I/O스트림의 구조적인 문제 가상머신을 거치는 부분은 해결하지 못했지만

각각의 버퍼에 받아 복사하고 전송하지 않고 한 버퍼에서 받고 보내기 할 수있어 성능향상은 이루어졌다고 볼 수 있다.



   ByteBuffer buf = ByteBuffer.allocateDirect(1024);   // Direct 버퍼 생성 - 가상머신 부분 생략 가능!!!

        : 파일  운영체제 버퍼  실행 중인 자바 프로그램








파일 랜덤 접근

(File Random Access)


파일 랜덤 접근

  파일에 데이터를 쓰거나 읽을  

  원하는 위치에 쓰거나 읽는 것을 의미한다


public static void main(String[] args) {

   Path fp = Paths.get("data.dat");    

   ByteBuffer wb = ByteBuffer.allocate(1024); // 버퍼 생성

   wb.putInt(120); // int 데이터 버퍼에 저장

   wb.putInt(240);

   wb.putDouble(0.94); // double 데이터 버퍼에 저장

   wb.putDouble(0.75);

   

   // 하나의 채널 생성

   try(FileChannel fc = FileChannel.open(fp, StandardOpenOption.CREATE,

          StandardOpenOption.READ, StandardOpenOption.WRITE)) {

      // 파일에 쓰기

      wb.flip();         // 버퍼에 어디까지 썻느냐에서 어디까지 읽었느냐로 바뀜 포지션이 다시 처음으로 이동!

      fc.write(wb);

     

      // 파일로부터 읽기

      ByteBuffer rb = ByteBuffer.allocate(1024); // 버퍼 생성

      fc.position(0); // 채널의 포지션을  앞으로 이동

 // 채널은 파일이 어디까지 썻느지 어디까지 읽었는지 포지션을 갖고있음

 // 채널이 파일에 쓰고난 후 다시 처음부터 읽기 위해 포지션을 처음으로 이동해야함.

      fc.read(rb);            // 버퍼에 채널을 통해 파일의 데이터를 처음부터 읽음

     

      // 이하 버퍼로부터 데이터 읽기

      rb.flip();                    // 버퍼에 어디까지 썻느냐에서 어디까지 읽었느냐로 바뀜. 포지션이 다시 처음으로 이동!

      System.out.println(rb.getInt());

      rb.position(Integer.BYTES * 2);         // 버퍼의 포지션 이동 - 두번째 int 건너뜀!

      System.out.println(rb.getDouble());

      System.out.println(rb.getDouble());

     

      rb.position(Integer.BYTES);         // 버퍼의 포지션 이동

      System.out.println(rb.getInt());

   } catch(IOException e) {

      e.printStackTrace();

   }

}


채널 및 버퍼의 포지션을 컨트롤 할 수 있다.



반응형