6.6 KiB
ByteBuffer란?
ByteBuffer는 Java NIO(Non-blocking I/O)에서 바이트 데이터를 효율적으로 다룰 수 있도록 제공하는 클래스이다.
기존의 InputStream 및 OutputStream 기반의 blocking I/O와 달리, **비동기적(Non-blocking)**으로 데이터를 처리할 수 있는 구조를 제공한다.
이 클래스는 **버퍼(Buffer)**라는 개념을 사용하여 데이터를 읽고 쓸 때 직접 메모리에서 조작할 수 있도록 하며, 성능 최적화에 유리하다.
특히 파일 I/O, 네트워크 전송, 바이너리 데이터 처리 등에서 활용된다.
1. ByteBuffer의 주요 특징
✔ 고정된 크기의 버퍼를 사용하여 데이터를 읽고 쓴다.
✔ Direct Buffer(직접 버퍼)와 Heap Buffer(힙 버퍼) 두 가지 방식이 있다.
✔ 데이터를 읽고 쓰는 포인터(위치, 한계, 용량)가 존재하여 데이터 흐름을 효율적으로 관리한다.
✔ 채널(Channel)과 함께 사용하여 고성능 I/O 처리가 가능하다.
2. ByteBuffer의 구조
ByteBuffer는 데이터를 저장하는 고정된 크기의 메모리 공간을 가지며, 다음과 같은 3가지 중요한 속성을 갖는다.
| 속성 | 설명 |
|---|---|
| position | 현재 읽거나 쓸 위치 (0부터 시작) |
| limit | 읽거나 쓸 수 있는 최대 위치 |
| capacity | 버퍼의 전체 크기 (변경 불가) |
🔹 주요 속성 변화 예시
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("초기 상태: " + buffer);
// 출력: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
위 상태에서 데이터를 put()하면 position이 증가하고, flip()을 호출하면 position이 0으로 초기화되어 데이터를 get()으로 읽을 준비를 한다.
3. ByteBuffer의 주요 메서드 정리
| 메서드 | 설명 |
|---|---|
allocate(int capacity) |
힙 메모리에 지정한 크기의 버퍼 생성 |
allocateDirect(int capacity) |
OS의 직접 메모리에 버퍼 생성 (성능 최적화) |
put(byte b) |
한 바이트 추가 |
put(byte[] src) |
바이트 배열을 버퍼에 추가 |
get() |
현재 위치의 바이트를 가져오고 position을 증가 |
get(byte[] dst) |
데이터를 바이트 배열로 가져옴 |
flip() |
쓰기 모드 → 읽기 모드 변경 (position=0, limit=현재 position) |
clear() |
버퍼를 초기화 (position=0, limit=capacity) |
rewind() |
읽기 모드에서 처음부터 다시 읽기 (position=0, limit 유지) |
compact() |
아직 읽지 않은 데이터는 유지한 채, 새로운 데이터를 쓸 수 있도록 position을 조정 |
mark() |
현재 position을 저장 |
reset() |
저장한 mark 위치로 position을 되돌림 |
4. ByteBuffer 사용 예제
(1) ByteBuffer 생성과 데이터 추가 (put(), flip(), get())
import java.nio.ByteBuffer;
public class ByteBufferExample {
public static void main(String[] args) {
// 10바이트 크기의 버퍼 생성
ByteBuffer buffer = ByteBuffer.allocate(10);
// 데이터 추가 (쓰기 모드)
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);
System.out.println("데이터 추가 후: " + buffer);
// 읽기 모드로 변경 (flip)
buffer.flip();
System.out.println("flip() 후: " + buffer);
// 데이터 읽기
System.out.println("읽은 데이터: " + buffer.get()); // 1
System.out.println("읽은 데이터: " + buffer.get()); // 2
System.out.println("get() 후: " + buffer);
}
}
✔ flip()을 호출하면 position이 0이 되어 데이터를 읽을 준비를 한다.
✔ get()을 호출할 때마다 position이 증가하여 다음 데이터를 읽을 수 있다.
(2) compact()와 clear() 차이점
import java.nio.ByteBuffer;
public class ByteBufferCompactClearExample {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte) 10);
buffer.put((byte) 20);
buffer.put((byte) 30);
buffer.flip(); // 읽기 모드 전환
System.out.println("flip() 후: " + buffer);
System.out.println("읽은 데이터: " + buffer.get()); // 10
System.out.println("읽은 데이터: " + buffer.get()); // 20
buffer.compact(); // 읽지 않은 데이터 유지한 채 쓰기 모드 변경
System.out.println("compact() 후: " + buffer);
buffer.clear(); // 완전히 초기화
System.out.println("clear() 후: " + buffer);
}
}
✔ compact()는 읽지 않은 데이터를 유지하고 position을 이동
✔ clear()는 전체를 초기화하고 position=0으로 변경
(3) DirectBuffer 사용 (allocateDirect())
import java.nio.ByteBuffer;
public class DirectByteBufferExample {
public static void main(String[] args) {
// DirectBuffer 생성
ByteBuffer directBuffer = ByteBuffer.allocateDirect(10);
directBuffer.put((byte) 42);
directBuffer.flip();
System.out.println("DirectBuffer 데이터: " + directBuffer.get()); // 42
}
}
✔ allocateDirect()를 사용하면 JVM의 힙 메모리가 아닌 OS의 직접 메모리에 할당하여 성능을 높일 수 있다.
✔ 다만, GC(가비지 컬렉터)가 자동으로 해제하지 않기 때문에 직접 관리해야 한다.
5. ByteBuffer vs. 일반 배열
| 비교 항목 | ByteBuffer |
일반 byte[] 배열 |
|---|---|---|
| 메모리 할당 | 힙(Heap) 또는 직접 메모리(Direct) | 힙 메모리 |
| 데이터 조작 | position/limit을 활용한 조작 | 단순한 인덱스 접근 |
| 성능 | allocateDirect() 사용 시 속도 향상 |
단순 배열보다 속도가 느릴 수 있음 |
| 가비지 컬렉션 | DirectBuffer는 수동 해제 필요 | 자동 관리 |
✔ 파일 I/O, 네트워크 프로그래밍에서는 ByteBuffer를 사용하는 것이 효율적이다.
✔ 단순한 배열 조작이라면 byte[]가 더 간단하다.
6. 결론
ByteBuffer는 Java NIO에서 고성능 I/O 처리를 위한 핵심 클래스로, 데이터를 읽고 쓰는 방식이 기존의 InputStream보다 더 유연하고 효율적이다.
특히 파일 I/O, 네트워크 전송, 바이너리 데이터 처리에 적합하며, DirectBuffer를 사용하면 성능을 극대화할 수 있다.