ByteBuffer란?
- 자바 NIO(Non-blocking I/O)에서 제공하는 클래스로, 바이트 배열에 대한 처리를 효율적으로 수행할 수 있게 돕는다.
- 바이트 데이터를 읽고 쓰는 데 사용되며, 네트워크 통신이나 파일 I/O에서 특히 유용하다.
- 네트워크 소켓에서 데이터를 읽거나 파일로부터 데이터를 읽는 등의 입출력 연산을 비동기적으로 수행할 수 있게 해준다. 이는 블로킹 I/O와 비교하여 효율적인 성능을 제공한다.
- 바이트 배열 외에도 원시 데이터 타입(primitive data types)을 읽고 쓸 수 있게 해준다.
- capacity(버퍼의 총 용량), limit(버퍼에서 읽거나 쓸 수 있는 최대 범위), position(현재 읽거나 쓰기를 수행하고 있는 위치) 등의 상태를 가지며, 이를 통해 바이트 데이터를 유연하게 조작할 수 있다.
- ByteBuffer는
DirectByteBuffer와
HeapByteBuffer
두 가지 주요 형태가 있다.DirectByteBuffer
는 자바 힙 외부에서 메모리를 할당받아 사용하며, 시스템 내부적으로 네이티브 I/O 연산을 효율적으로 수행할 수 있다. 반면,HeapByteBuffer
는 자바 힙 내부에 데이터를 저장하는 버퍼이다.
ByteBuffer의 특징
- 커널 버퍼에 직접 접근할 수 있는 NIO의 장점을 이용하기 위해서는 ByteBuffer 클래스만 Direct Buffer를 지원한다.
- 즉, 커널 버퍼에 직접 접근할 수 있는 NIO의 장점을 이용하기 위해서는 ByteBuffer의 allocateDirect()라는 메서드를 이용해서 ByteBuffer를 만들어 내야 한다.
- 자바가 C에 비해 느린 이유 중 하나는 IO가 JVM 내부에 IO 버퍼를 두었기 때문인데, ByteBuffer를 사용함으로 속도 향상이 가능하다.
- 특히 네트워크 통신 등 byte배열 사용이 잦은 경우 GC가 성능에 영향을 끼질 수 있지만 ByteBuffer는 이러한 상황을 예방해줄 수 있다.
java.nio의 Buffer 계층도
ByteBuffer의 생성 방법 3가지
- allocate : JVM의 힙 영역에 바이트 버퍼를 생성한다. 인수는 생성할 바이트 버퍼 크기다.
- 생성시간이 빠르지만, 입출력 성능은 낮다
- allocateDirect : JVM 힙 영역이 아닌 운영체제의 커널 영역에 바이트 버퍼를 생성한다.
- 생성시간이 느리지만, 입출력 성능은 높다
- wrap : 입력된 바이트 배열을 사용하여 바이트 버퍼를 생성한다. 입력에 사용된 바이트 배열이 변경되면 wrap을 사용하여 생성한 바이트 버퍼도 변경된다
ByteBuffer의 네 가지 포인터
- ByteBuffer에는 위치를 나타내는 네가지 포인터가 있다.
- position, limit, capacity, mark
- 0 <= mark <= position <= limit <= capacity
- position : 현재 읽을 위치나 현재 쓸 위치를 가리킨다. ByteBuffer에서 get()함수로 읽기를 시도할 경우 position 위치부터 읽기 시작하여, put()함수로 ByteBuffer에 쓰기를 시도할 경우 position 위치부터 쓰기를 시작한다. 읽거나 쓸 때마다 position의 위치는 자동으로 이동한다
- limit : 현재 ByteBuffer의 유요한 쓰기 위치나 유효한 읽기 위치를 나타낸다. 다시 말해 “이 버퍼는 여기까지 읽을 수 있습니다” 혹은 “여기까지 쓸 수 있습니다”를 나타낸다.
- capacity : ByteBuffer의 용량을 나타낸다. 따라서 항상 ByteBuffer의 맨 마지막을 가리키고 있다. 그 때문에 position과 limit와는 달리 그 위치를 바꿀 필요가 없다
- mark : 편리한 표인터이다. 특별한 의미가 있는 건 아니고 사용자가 마음대로 지정할 수 있다. 특별히 이 위치를 기억하고 있다가 다음데 되돌아가야할 때 사용한다.
java.nio.Buffer의 주요 메서드
clear()
-> return Buffer- 말 그대로 초기화
- 포지션을 0으로 설정
flip()
-> return Buffer- 마지막으로 썼던 버퍼를 읽기 좋게 변경
- 포지션을 0으로 설정
- limit을 현재 내용의 마지막 위치로 압축
hasRemaining()
-> return Buffer- 포지션과 리미트가 같지 않은지 확인
- 버퍼내 내용이 있으면 true
rewind()
-> return Buffer- 포지션은 맨 처음으로 초기화 됨
- 포지션이 0이 아닐 때 0으로 위치시킴
mark()
-> return Position- 현재 위치(P)를 임시 저장한다.
reset()
-> return Position- 임시저장된 위치로 다시 P를 옮겨놓는다.
- mark-reset은 주로 데이터를 미리 살짝 읽어볼 때 (Peek) 주로 쓴다
- reset과 clear는 헷갈리기 쉬우니 주의
java.nio.ByteBuffer의 주요 메서드
compact()
-> abstact ByteBuffer- 포지션과 리미트를 앞당기고 다음 포지션과 리미트를 설정
wrap(byte[] array)
-> static ByteBuffer- byte[]를 byteBuffer로 만듦
- 만들어진 ByteBuffer는 byte[]에 의존하게 되며, capcaity, limit은 byte[]의 사이즈와 같음
ByteBuffer의 get()/put()
get()
,put()
은 각각 버퍼에 데이터를 읽고 쓰는 메서드이다get()
,put()
은 다시 상대적/절대적으로 구분된다- 상대적
get()
,put()
: position값 증가 - 절대적
get()
,put()
: position값 변화없음
- 상대적
예시 (CharBuffer 사용)
- P : Position, L : Limit C : Capacity
allocate
- 8개의 글자가 들어갈 수 있는 버퍼를 만든다. allocate하면 버퍼는 현재 쓰기모드이다
1
2
3
4
CharBuffer buf = CharBuffer.allocate(8); // capacity : 8
// P L
// |0|1|2|3|4|5|6|7|
// | | | | | | | | |
put
- put을 사용할 경우 Position이 이동하면서 버퍼에 데이터가 들어간다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// P L
// |0|1|2|3|4|5|6|7|
// | | | | | | | | |
buf.put('a'); buf.put('b'); buf.put('c'); buf.put('e');
// P------>P L
// |0|1|2|3|4|5|6|7|
// |a|b|c|e| | | | |
buf.put(3,'d');
// P L
// |0|1|2|3|4|5|6|7|
// |a|b|c|d| | | | |
flip
- 입력된 데이터를 읽기 위해 read-mode로 바꾼다. (읽기 좋게 바꾼다)
- 버퍼의 시작이자 데이터의 시작인 0으로 Position을 이동하고, 데이터의 끝 부분에 L이 위치하게 된다.
- Limit이 Position이 되고, Position이 0이 된다.
1
2
3
4
5
6
7
8
9
10
11
12
// P L
// |0|1|2|3|4|5|6|7|
// |a|b|c|d| | | | |
buf.flip();
// L<------L
// P<------P
// |0|1|2|3|4|5|6|7|
// |a|b|c|d| | | | |
// L = P
// P = 0
get
- Position을 이동시키며 데이터를 읽는다
1
2
3
4
5
6
7
8
9
10
// P L
// |0|1|2|3|4|5|6|7|
// |a|b|c|d| | | | |
char a, b, c;
a = buf.get(); b = buf.get(); c = buf.get();
// P---->P L
// |0|1|2|3|4|5|6|7|
// |a|b|c|d| | | | |
compact
- 데이터를 쓰기모드로 바꾼다.
- 읽지 않은 데이터 ‘d’가 복사되고, ‘d’의 바로 뒤에 쓸 수 있도록 Position이 변했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// P L
// |0|1|2|3|4|5|6|7|
// |a|b|c|d| | | | |
buf.compact();
// P<--P
// L------>L
// |0|1|2|3|4|5|6|7|
// |d| | | | | | | |
// 남아있는 데이터를 처음으로 복사
// P = 남아있는 데이터 수
// L = C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class ByteBufferExample {
public static void main(String[] args) {
// 문자열
String input = "Hello, ByteBuffer";
// 문자열을 바이트 배열로 변환
byte[] inputData = input.getBytes(StandardCharsets.UTF_8);
// ByteBuffer를 생성
ByteBuffer buffer = ByteBuffer.allocate(inputData.length);
// 데이터를 ByteBuffer에 쓰기
buffer.put(inputData);
// 버퍼를 flip하여 읽기 모드로 전환
buffer.flip();
// ByteBuffer에서 데이터 읽기
byte[] outputData = new byte[buffer.remaining()];
buffer.get(outputData);
// 읽은 데이터를 문자열로 변환
String output = new String(outputData, StandardCharsets.UTF_8);
System.out.println("Input: " + input);
System.out.println("Output: " + output);
}
}
Comments powered by Disqus.