본문 바로가기

공부/Java

NIO ??

NIO 소개

NIO : 새로운 입출력

1. IO와 NIO의 차이점

방식의 차이가 있음

NIO 입출력 방식 : 채널 방식 버퍼 방식 : 버퍼 비동시 방식 : 지원 블로킹 / 넌블로킹 방식 : 모두 지원

IO 입출력 방식 : 스트림 방식 버퍼 방식 : 넌버퍼 비동시 방식 : 지원 안 함 블로킹 / 넌블로킹 방식 : 블로킹 방식만 지원

스트림 vs 채널

NIO는 채널 기반 : 스트림과 달리 양방향으로 입력과 출력이 가능하다. 그렇기 때문에 입력과 출력을 위한 별도의 채널을 만들 필요가 없음 ex) 하나의 파일에서 데이터를 읽고 저장하는 작업을 모두 해야 한다면 FileChannel 하나만 생성하면 됨(스트림은 FileInputStream, FileOutputStream 필요)

NIO는 버퍼 사용

IO에서는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽음 -> 대체로 느림 버퍼(Buffer : 메모리 저장소)를 사용해서 복수 개의 바이트를 한꺼번에 입력받고 출력하는 것이 빠른 성능을 냄 채넗은 버퍼에 저장된 데이터를 출력하고, 입력된 데이터를 버퍼에 저장함

NIO는 읽은 데이터를 무조건 버퍼에 저장하기 때문에 버퍼 내에서 데이터의 위치를 이동해 가면서 필요한 부분만 읽고 쓸 수 있음

IO는 블로킹 된다. 입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 스레드는 블로킹(대기 상태) 된다. 마찬가지로 출력 스트림의 write() 메소드를 호출하면 데이터가 출력되기 전까지 스레드는 블로킹 된다. NIO는 블로킹과 넌블로킹 특징을 모두 가지고 있음 NIO블로킹은 스레드를 인터럽트 함으로써 빠져나올 수가 있음 NIO의 넌블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 스레드가 처리하기 때문에 작업 스레드가 블로킹 되지 않음 작업 준비가 완료되었다는 것 = 바로 읽고 쓸 수 있는 상태 핵심객체 : Multiplexor, Selector

버퍼

NIO에서는 데이터를 입출력하기 위해 항상 버퍼를 사용해야 한다. 버퍼 : 읽고 쓰기가 가능한 메모리 배열

버퍼 종류

넌다이렉트 버퍼 : JVM이 관리하는 힙 메모리 공간을 이요하는 버퍼 다이렉트 버퍼 : 운영체제가 관리하는 메모리 공간을 이용하는 버퍼

Buffer 생성
  1. allocate() 메소드 : JVM 힙 메모리에 넌 다이렉트 버퍼를 생성

ex) ByteBuffer.allocate(int capacity) -> capacity 개만큼의 byte값을 저장

  1. wrap() 메소드 : 이미 생성되어있는 자바 배열을 래핑해서 Buffer 객체를 생성함 : 자바 배열은 JVM 힙 메모리에 생성되므로 wrap()은 넌다이렉트 버퍼를 생성함

ex)

byte[] byteArray = new byte[100];
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray, 0, 50);
// -> 일부 인덱스만 버퍼로 생성
  1. allocateDirect() 메소드 : 운영체제가 관리하는 메모리에 다이렉트 버퍼를 생성 : ByteBuffer만 제공 : 각 타입별 다이렉트 버퍼를 생성하고 싶다면 우선 ByteBuffer 생성 후, asCharBuffer() .... 메소드를 타입별 버퍼을 얻을 수 있음

ex)

// 100개의 byte값을 저장
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
// 50개의 char값 저장
CharBuffer charBuffer = ByteBuffer.allocateDirect(100).asCharBuffer();
// 25개의 int값 저장
IntBuffer intBuffer = ByteBuffer.allocateDirect(100).asIntBuffer();
  1. byte 해석순서(ByteOrder) : 데이터를 처리할 때 바이트 처리 순서는 운영체제마다 차이가 있음 : 이러한 차이는 데이터를 다른 운영체제로 보내거나 받을 때 영향을 미치기 때문에 데이터를 다루는 버퍼도 이를 고려해야 함 : 앞쪽 바이트부터 먼저 처리하는 것 = Big endian : 뒤쪽 바이트부터 먼저 처리하는 것 = Little endian -> Little endian으로 동작하는 운영체제에서 만든 데이터 파일을 Big endian으로 동작하는 운영체제에서 읽는다면 ByteOrder 클래스로 데이터 순서를 맞춰야함 -> JVM도 일종의 독립된 운영체제이기 때문에 이런 문제를 취급하며, JRE가 설치된 어떤 환경이든 JVM은 무조겅 Big endian으로 동작하도록 되어 있음 -> 운영체제와 JVM의 해석 순서가 다를 경우에는 JVM이 운영체제와 데이터를 교환할 때 자동적으로 처리해주기 떄문에 문제 없지만, 다이렉트 버퍼일 경우 운영체제의 native I/O를 사용하므로 운영체제의 기본 해석 순서를 맞추는 것이 성능에 도움이 됨

ex)

 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100).order(ByteOrder.nativeOrder());

Buffer의 위치 속성

  1. position : 현재 읽거나 쓰는 위치 : 인덱스 값이기 때문에 0부터 시작하며, limit보다 큰값을 가질 수 없음 : 만약 position과 limit값이 같아진다면 더 이상 데이터를 쓰거나 읽을 수 없음

  2. limit : 버퍼에서 읽거나 쓸 수 있는 위치의 한계를 나타냄 : capacity 보다 작거나 같은 값을 가짐 : 최초에 버퍼를 만들었을 때는 capacity와 같은 값을 가짐

  3. capacity : 버퍼의 최대 데이터 개수(메모리 크기)를 나타냄 : 인덱스가 아닌 수량

  4. mark : reset() 메소드를 실행했을 때 돌아오는 위치를 저장하는 인덱스로 mark() 메소드로 지정 가능함 : position 이하로 지정해야하며, position 이나 limit값보다 작으면 자동 제거됨 : mark 가 없는 상태에서 reset() 메소드를 호출하면 InvalidMarkException이 발생함

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

Buffer 메소드

  1. 데이터를 읽고 저장하는 메소드 put() : 데이터를 저장 get() : 데이터를 읽음

-> 해당 메소드는 상대적과 절대적으로 구분됨 -> position 에서 데이터를 읽고 저장하면 상대적 -> position 과 상관없이 주어진 인덱스에서 데이터를 읽고 저장하면 절대적 -> 메소드에 인덱스 매개 변수 유무로 구분 가능함

  1. 버퍼 예외의 종류 BufferOverflowException : position이 limit에 도달했을 때 put()을 호출하면 발생 BufferUnderflowException : position이 limit에 도달했을 때 get()을 호출하면 발생 InvalidMarkException : mark가 없는 상태에서 reset() 메소드를 호출하면 발생 ReadOnlyBufferException : 읽기 전용 버퍼에서 put() 또는 compact() 메소드를 호출하면 발생

  2. Buffer 변환 : 채널이 데이터를 읽고 쓰는 버퍼는 모두 ByteBuffer임 : 그러므로 채널을 통해 읽은 데이터를 복원하려면 ByteBuffer를 문자열로 또는 다른 타입의 버퍼로 변환해야함

'공부 > Java' 카테고리의 다른 글

[LMAX-Exchange/disruptor] ringbuffer 사용하기  (0) 2019.09.16
sigar 라이브러리  (0) 2019.08.06
Apache POI 사용한 엑셀 파일 생성  (0) 2019.07.10
Optional 사용하여 null 해결하기  (0) 2019.03.03