NIO에서는 데이터를 입출력하기 위해 항상 버퍼를 사용해야 한다.
Buffer는 저장되는 데이터 타입에 따라 분류될 수 있고 어떤 메모리를 사용하느냐에 따라 다이렉트(Direct)
와 넌다이렉트(NonDirect)
로 분류할 수도 있다.
NIO 버퍼는 저장되는 데이터 타입에 따라서 별도의 클래스로 제공된다.
Buffer
추상 클래스를 모두 상속하고 있다.버퍼 클래스의 이름을 보면 어떤 데이터가 저장되는 버퍼인지 쉽게 알 수 있다.
ByteBuffer
, CharBuffer
, ShortBuffer
, IntBuffer
, LongBuffer
, FloatBuffer
, DoubleBuffer
는 각각 byte
, char
, int
, long
, float
, double
데이터가 저장되는 버퍼이다.MappedByteBuffer
는 ByteBuffer
의 하위 클래스로 파일의 내용에 랜덤하게 접근하기 위해서 파일의 내용을 메모리와 맵핑시킨 버퍼이다.버퍼가 사용하는 메모리의 위치에 따라서
넌다이렉트(non-direct) 버퍼
와다이렉트(direct) 버퍼
로 분류된다.
넌다이렉트 버퍼
는 JVM이 관리하는 힙 메모리 공간을 이용하는 버퍼이고, 다이렉트 버퍼
는 운영체제가 관리하는 메모리 공간을 이용하는 버퍼이다.구분 | 넌다이렉트 버퍼 | 다이렉트 버퍼 |
---|---|---|
사용하는 메모리 공간 | JVM의 힙 메모리 | 운영체제의 메모리 |
버퍼 생성 시간 | 버퍼 생성 빠름 | 버퍼 생성 느림 |
버퍼의 크기 | 작음 | 큼 (큰 데이터를 처리할 때 유리) |
입출력 성능 | 낮음 | 높음 (입출력이 빈번할 때 유리) |
네이티브(native) C 함수
를 호출해야 하고 여러 가지 잡다한 처리를 해야 하므로 상대적으로 버퍼 생성이 느리다.native I/O
기능을 수행한다. 그렇기 때문에 직접 다이렉트 버퍼를 사용하는 것보다는 입출력 성능이 낮다.native I/O
를 수행한다. 만약 채널을 사용하지 않고 ByteBuffer
의 get()
/put()
메소드를 사용해서 버퍼의 데이터를 읽고, 저장한다면 이 작업은 내부적으로 JNI를 호출해서 native I/O
를 수행하기 때문에 JNI 호출이라는 오버 헤더가 추가된다. 그렇기 때문에 오히려 넌다이렉트 버퍼의 get()
/put()
메소드 성능이 더 좋게 나올 수도 있다.📌
JNI(Java Native Interface)
는 자바 코드에서 C함수를 호출할 수 있도록 해주는 API이다.
각 데이터 타입별로
- 넌다이렉트 버퍼를 생성하기 위해서는 각
Buffer
클래스의allocate()
와wrap()
메소드를 호출하면 되고,- 다이렉트 버퍼는
ByteBuffer
의allocateDirect()
메소드를 호출하면 된다.
JVM 힙 메모리에 넌다이렉트 버퍼를 생성한다.
리턴 타입 | 메소드(매개 변수) | 설명 |
---|---|---|
ByteBuffer | ByteBuffer.allocate(int capacity) | capacity개만큼의 byte값을 저장 |
CharBuffer | CharBuffer.allocate(int capacity) | capacity개만큼의 char값을 저장 |
DoubleBuffer | DoubleBuffer.allocate(int capacity) | capacity개만큼의 double 값을 저장 |
FloatBuffer | FloatBuffer.allocate(int capacity) | capacity개만큼의 float값을 저장 |
IntBuffer | IntBuffer.allocate(int capacity) | capacity개만큼의 int값을 저장 |
LongBuffer | LongBuffer.allocate(int capacity) | capacity개만큼의 long 값을 저장 |
ShortBuffer | ShortBuffer.allocate(int capacity) | capacity개만큼의 short값을 저장 |
ByteBuffer
를 생성하고, 최대 100개의 문자를 저장하는 CharBuffer
를 생성ByteBuffer byteBuffer = ByteBuffer.allocate(100);
CharBuffer charBuffer = CharBuffer.allocate(100);
각 타입별 Buffer
클래스는 모두 wrap()
메소드를 가지고 있는데, wrap()
메소드는 이미 생성되어 있는 자바 배열을 래핑해서 Buffer
객체를 생성한다. 자바 배열은 JVM 힙 메모리에 생성되므로 wrap()
은 넌다이렉트 버퍼를 생성한다.
byte[]
를 이용하여 ByteBuffer
를 생성하고, 길이가 100인 char[]
를 이용해서 CharBuffer
생성byte[] byteArray = new byte[100];
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
char[] charArray = new char[100];
CharBuffer charBuffer = CharBuffer.wrap(charArray)l
Buffer
객체를 생성할 수도 있다. 이 경우 시작 인덱스와 길이를 추가적으로 지정하면 된다. byte[] byteArray = new byte[100];
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray, 0, 50);
char[] charArray = new char[100];
CharBuffer charBuffer = CharBuffer.wrap(charArray, 0, 50);
CharBuffer
는 추가적으로 CharSequence
타입의 매개값으로 갖는 wrap()
메소드도 제공한다. String이 CharSequence
인터페이스를 구현했기 때문에 매개값으로 문자열을 제공해서 다음과 같이 CharBuffer
를 생성할 수도 있다.CharBuffer charBuffer = CharBuffer.wrap("NIO 입출력은 버퍼를 이용한다.");
ByteBuffer
의allocateDirect()
메소드는 JVM 힙 메모리 바깥쪽, 즉 운영체제가 관리하는 메모리에 다이렉트 버퍼를 생성한다.
ByteBuffer
에서만 제공된다. ByteBuffer
의 allocateDirect()
메소드로 버퍼를 생성한 다음 ByteBuffer
의 asCharBuffer()
, asShortBuffer()
, asIntBuffer()
, asLongBuffer()
, asFloatBuffer()
, asDoubleBuffer()
메소드를 이용해서 해당 타입별 Buffer
를 얻으면 된다.100개의 바이트(byte)
를 저장하는 다이렉트 ByteBuffer
와 50개의 문자(char)
를 저장하는 다이렉트 CharBuffer
, 25개의 정수(int)
를 저장하는 다이렉트 IntBuffer
생성char
는 2바이트
크기를 가지고, int
는 4바이트
크기를 가지기 때문에 초기 다이렉트 ByteBuffer
생성 크기에 따라 저장 용량이 결정된다.//100개의 byte값 저장
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
//50개의 char값 저장
CharBuffer charBuffer = ByteBuffer.allocateDirect(100).asCharBuffer();
//25개의 int값 저장
IntBuffer intBuffer = ByteBuffer.allocateDirect(100).asIntBuffer();
데이터를 처리할 때 바이트 처리 순서는 운영체제마다 차이가 있다. 이러한 차이는 데이터를 다른 운영체제로 보내거나 받을 때 영향을 미치기 때문에 데이터를 다루는 버퍼도 이를 고려해야 한다.
Big Endian
, Little Endian
이라고 한다.Little Endian
으로 동작하는 운영체제에서 만든 데이터 파일을 Big Endian
으로 동작하는 운영체제에서 읽는다면 ByteOrder
클래스로 데이터 순서를 맞춰야 한다. ByteOrder
클래스의 nativeOrder()
메소드는 현재 동작하고 있는 운영체제가 Big Endian
인지 Little Endian
인지 알려준다. JVM도 일종의 독립된 운영체제이기 때문에 이런 문제를 취급하는데, JRE가 설치된 어떤 환경이든 JVM은 무조건 Big Endian
으로 동작하도록 되어 있다.
운영체제가 JVM의 바이트 해석 순서가 다를 경우에는 JVM이 운영체제와 데이터를 교환할 때 자동적으로 처리해주기 때문에 문제는 없다. 하지만 다이렉트 버퍼일 경우 운영체제의 native I/O
를 사용하므로 운영체제의 기본 해석 순서로 JVM의 해석 순서를 맞추는 것이 성능에 도움이 된다.
다음과 같이 allocateDirect()
로 버퍼를 생성한 후, order()
메소드를 호출해서 nativeOrder()
의 리턴값으로 세팅해주면 된다.
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100).order(ByteOrder.nativeOrder()));
이것이 자바다 책