티스토리 뷰
Overview
Java에서 제공하는 ByteBuffer에 대해 알아보고, 생성해봅니다.
ByteBuffer란?
ByteBuffer 는 바이트 데이터를 저장하고 읽는 저장소입니다.
배열을 멤버변수로 가지고 있고, 배열에 대한 읽기/쓰기를 지원합니다. CharBuffer, IntBuffer, LongBuffer.. 등 데이터 타입에 따라 다양한 추상 메소드를 제공합니다.
배열상태 관리
- capacity
버퍼에 저장할 수 있는 데이터의 최대 크기입니다. 한 번 정하면 변경할 수 없고, 생성자의 파라미터로 입력받아 버퍼를 생성합니다. - position
읽기/쓰기가 작업 중인 위치를 나타내는 필드입니다. 버퍼가 생성될 때 0으로 초기화되고, put 메소드나 get 메소드가 호출되면 자동으로 증가합니다. limit과 capacity 값보다 작거나 같습니다. - limit
읽고 쓸 수 있는 버퍼 공간의 최대치를 나타냅니다. limit 메소드로 값을 수정할 수 있고, capacity 보다 크게 설정할 수 없습니다.
ByteBuffer 생성
allocate
해당 메소드를 통해 생성 된 버퍼는 JVM의 힙영역에 버퍼를 할당합니다. 힙 버퍼라고 부르며, 일반적으로 자바에서 버퍼를 할당할 때 사용하는 메소드입니다.allocateDirect
JVM의 힙 영역이 아닌 운영체제의 커널 영역에 바이트 버퍼를 생성합니다. 다이렉트 버퍼라고 부르며, ByteBuffer로만 생성할 수 있습니다. 힙버퍼에 비해 생성 시간은 길지만 성능상 이점을 가지고 있습니다.
JVM 영역 외에 운영체제에 할당 되는 버퍼이다보니, 운영체제가 제공하는 입/출력 기능을 직접 사용하기 때문에 줄어들어 성능면에서 이점을 가질수 있습니다.wrap
입력된 바이트 배열을 사용하여 버퍼를 생성합니다. 내부적으로 HeapByteBuffer를 통하여 생성하며, 입력에 사용된 바이트 배열이 변경되면 wrap를 사용해서 생성한 바이트 버퍼도 변경됩니다.
3가지 메소드를 사용하여 ByteBuffer를 생성해 보았습니다. 눈여겨 봐야할 점은 get 혹은 put 메소드를 사용하면 position의 값이 같이 공유된다는 점입니다. put을 2번 하여 position이 2로 예상 되지만, 실제 출력되는 값은 3입니다. 하나의 버퍼에 읽기/쓰기를 공유하기 때문입니다.
@Test
public void create_ByteBuffer_ByHeap(){
ByteBuffer heapBuffer = ByteBuffer.allocate(15);
assertEquals(15, heapBuffer.capacity());
assertEquals(15, heapBuffer.limit());
assertEquals(0, heapBuffer.position());
assertEquals(false, heapBuffer.isDirect());
}
@Test
public void create_ByteBuffer_ByDirect(){
ByteBuffer directBuffer = ByteBuffer.allocateDirect(15);
directBuffer.put((byte)10);
directBuffer.put((byte)11);
directBuffer.get();
directBuffer.limit(10);
assertThrows(IllegalArgumentException.class,
() -> directBuffer.limit(20));
assertEquals(15, directBuffer.capacity());
assertEquals(10, directBuffer.limit());
assertEquals(true, directBuffer.isDirect());
assertEquals(3, directBuffer.position());
}
@Test
public void create_ByteBuffer_ByWrap(){
byte[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
assertEquals(15, byteBuffer.capacity());
assertEquals(15, byteBuffer.limit());
assertEquals(false, byteBuffer.isDirect());
}
flip
flip 메소드는 이전에 get/put 메소드를 호출하여 변경 된 position 정보를 저장하기 위해 사용합니다. 메소드를 호출하게 되면 limit이 마지막에 기록된 데이터의 위치로 변경이 됩니다. 또한 position이 0으로 초기화됩니다.
@Test
public void Write_And_Flip(){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(15);
assertEquals(0, byteBuffer.position());
assertEquals(15, byteBuffer.limit());
byteBuffer.put((byte)1);
byteBuffer.put((byte)2);
byteBuffer.put((byte)3);
assertEquals(3, byteBuffer.position());
assertEquals(15, byteBuffer.limit());
byteBuffer.flip();
assertEquals(0, byteBuffer.position());
assertEquals(3, byteBuffer.limit());
assertEquals(15, byteBuffer.capacity());
}
clear
메소드명 그대로 Buffer를 클리어합니다. position은 0으로 세팅하고, limit을 capacity값과 동일하게 초기화합니다.
@Test
public void Write_And_Clear(){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(15);
byteBuffer.put((byte)99);
byteBuffer.put((byte)88);
byteBuffer.put((byte)77);
byteBuffer.limit(7);
byteBuffer.clear();
assertEquals(0, byteBuffer.position());
assertEquals((byte)99, byteBuffer.get(0));
assertEquals((byte)88, byteBuffer.get(1));
assertEquals((byte)77, byteBuffer.get(2));
assertEquals(15, byteBuffer.limit());
assertEquals(15, byteBuffer.capacity());
}
rewind
position을 0으로 세팅합니다. clear 메소드와 다른점은 limit값은 초기화 하지 않습니다.
@Test
public void Write_And_Rewind(){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(15);
byteBuffer.put((byte)99);
byteBuffer.put((byte)88);
byteBuffer.put((byte)77);
byteBuffer.limit(7);
byteBuffer.rewind();
assertEquals(0, byteBuffer.position());
assertEquals((byte)99, byteBuffer.get(0));
assertEquals((byte)88, byteBuffer.get(1));
assertEquals((byte)77, byteBuffer.get(2));
assertEquals(7, byteBuffer.limit());
assertEquals(15, byteBuffer.capacity());
}
Appendix
ByteBuffer allocate / allocateDirect 생성속도 테스트
정말 차이가 날까? 다른 블로그 개발자님의 테스트를 참고해서 ByteBuffer를 생성하여 테스트 해보았습니다.
@Test
public void Run_10Times(){
for(int i = 0 ; i < 10 ; i++){
PrintTime_When_allocate();
}
for(int i = 0 ; i < 10 ; i++){
PrintTime_When_allocateDirect();
}
}
@Test
public void PrintTime_When_allocate(){
long startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000 ; i++){
ByteBuffer.allocate(1024);
}
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
System.out.println("allocate elapsedTime = " + elapsedTime);
}
@Test
public void PrintTime_When_allocateDirect(){
long startTime = System.currentTimeMillis();
for(int i = 0; i < 1000000 ; i++){
ByteBuffer.allocateDirect(1024);
}
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
System.out.println("allocateDirect elapsedTime = " + elapsedTime);
}
각각 ByteBuffer를 1000000번씩 생성하여 10번을 테스트 해보았습니다.
결과는 아래와 같이 약 10배정도 차이나는것을 확인할수 있습니다.(테스트 환경에 따라 다름 주의)
allocate elapsedTime = 183
allocate elapsedTime = 119
allocate elapsedTime = 107
allocate elapsedTime = 110
allocate elapsedTime = 116
allocate elapsedTime = 128
allocate elapsedTime = 126
allocate elapsedTime = 108
allocate elapsedTime = 106
allocate elapsedTime = 127
allocateDirect elapsedTime = 1440
allocateDirect elapsedTime = 865
allocateDirect elapsedTime = 912
allocateDirect elapsedTime = 949
allocateDirect elapsedTime = 953
allocateDirect elapsedTime = 933
allocateDirect elapsedTime = 831
allocateDirect elapsedTime = 1076
allocateDirect elapsedTime = 1087
allocateDirect elapsedTime = 1091
참고
'Programming > JAVA' 카테고리의 다른 글
Java Stream API 사용해보기 (407) | 2021.05.25 |
---|---|
Immutable Object(불변 객체) (405) | 2021.04.06 |
String, StringBuffer, StringBuilder의 차이점이 뭘까? (0) | 2020.08.07 |
네이버 책 검색 OPEN API 활용하기 (820) | 2016.06.23 |
[JAVA] TCP 1:1 채팅 (0) | 2016.04.18 |