Files
java-examples/docs/ByteBuffer.md

6.6 KiB

ByteBuffer란?

ByteBuffer는 Java NIO(Non-blocking I/O)에서 바이트 데이터를 효율적으로 다룰 수 있도록 제공하는 클래스이다.
기존의 InputStreamOutputStream 기반의 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. 결론

ByteBufferJava NIO에서 고성능 I/O 처리를 위한 핵심 클래스로, 데이터를 읽고 쓰는 방식이 기존의 InputStream보다 더 유연하고 효율적이다.
특히 파일 I/O, 네트워크 전송, 바이너리 데이터 처리에 적합하며, DirectBuffer를 사용하면 성능을 극대화할 수 있다.