버퍼(Buffers)는 0 또는 그 이상으로 표현되는 바이트(Bytes)의 연속입니다.
버텍스의 버퍼는 바이트를 Write하는 과정에서 자동으로 크기가 확장되는 일종의 스마트 바이트 배열입니다.
버퍼 초기화
버퍼는 정적 메소드 Buffer.buffer()를 사용하여 생성합니다. 빈 버퍼를 생성하려면 다음과 같이 작성합니다.
Buffer buff = Buffer.buffer();
문자열을 사용하여 초기화도 가능합니다. 문자열은 UTF-8로 인코딩됩니다.
Buffer buff = Buffer.buffer("some string");
문자열의 인코딩 유형을 지정하려면 다음과 같이 작성합니다.
Buffer buff = Buffer.buffer("some string", "UTF-16");
바이트 배열(byte[])을 사용하는 경우 다음과 같이 작성합니다.
byte[] bytes = new byte[] {1, 3, 5};
Buffer buff = Buffer.buffer(bytes);
버퍼가 사용할 메모리 사이즈를 예측 할 수 있다면 필요한 메모리를 사전에 할당 할 수 있습니다.
사전에 예측한대로 할당된 메모리 내에서 버퍼가 Write 된다면 자동 확장으로 인한 과정보다 더 효율적입니다.
Buffer buff = Buffer.buffer(10000);
버퍼 읽기/쓰기
버퍼에 데이터를 작성하는 방법은 Appending과 Random access 두 가지가 있습니다.
Appending은 작성하는 데이터 유형에 따라서 appendXXX 메소드를 사용합니다. 이 방법으로 Write되는 데이터는 버퍼의 뒤에 붙여집니다.
Buffer buff = Buffer.buffer();
buff.appendInt(123);
buff.appendString("hello\n");
Random access는 작성하는 데이터 유형에 따라서 setXXX 메소드를 사용합니다. 메소드의 첫 번째 인자는 접근하려는 버퍼의 인덱스입니다.
이 방법으로 Write되는 데이터는 지정된 버퍼의 인덱스를 시작으로 이전 데이터를 덮어씌웁니다.
Buffer buff = Buffer.buffer();
buff.setInt(1000, 123);
buff.setString(0, "hello");
어느 경우든 버퍼는 항상 바이트를 Write 할 수 있도록 자동으로 확장되므로 쓰기 작업 중에는 IndexOutOfBoundsException을 발생시키지 않습니다.
다음 예시는 빈 버퍼를 생성하고 10000 인덱스에 엑세스 후 Int형 데이터 1을 작성합니다.
버퍼에 엑세스하며 10000 바이트를 할당하고, Int형 데이터를 추가하기 위해 4 바이트를 추가로 확보하므로 버퍼의 크기는 10004 바이트가 됩니다.
Buffer b = Buffer.buffer();
b.setInt(10000, 1);
System.out.println(b.length());
버퍼의 데이터를 읽으려면 데이터 유형에 따라서 getXXX 메소드를 사용합니다. 메소드의 첫 번째 인자는 접근하려는 버퍼의 인덱스입니다.
Buffer buff = Buffer.buffer();
for (int i = 0; i < buff.length(); i += 4)
{
System.out.println("int value at " + i + " is " + buff.getInt(i));
}
코드 | 비고 | |
버퍼 전체에 걸쳐서 Int 데이터를 얻기 위해 4바이트씩 건너뛰며 엑세스합니다. |
부호가 없는 정수
부호가 없는 정수는 getUnsignedXXX appendUnsignedXXX 및 setUnsignedXXX 메소드를 사용합니다.
대게 대역폭 소모를 최소화 하기 위한 네트워크 프로토콜 구현 코덱(Codecs)에 사용됩니다. 다음 예시는 정수 값 200을 1바이트만으로 저장하고 읽습니다.
Buffer buff = Buffer.buffer(128);
int pos = 15;
buff.setUnsignedByte(pos, (short) 200);
System.out.println(buff.getUnsignedByte(pos));
length(), copy(), slice()
버퍼에서 많이 사용되는 함수 중 하나인 length()는 버퍼에 할당된 바이트 배열의 길이를 리턴합니다.
Buffer b = Buffer.buffer(1000);
// 1000
System.out.println(b.length());
copy()를 사용하면 원본 버퍼의 전체 바이트 배열을 다른 버퍼에 복사 할 수 있습니다.
이때 데이터가 복사되는 방식은 값에 의한 복사 | 얕은 복사(Shallow copy)입니다.
Buffer buffer = Buffer.buffer();
buffer.appendInt(100);
// 버퍼를 copy() 메소드로 복사
Buffer copied = buffer.copy();
// buffer: 100 & copied: 100
System.out.println(buffer.getInt(0));
System.out.println(copied.getInt(0));
// 원래 버퍼의 값 수정
buffer.setInt(0, 200);
// buffer: 200 & copied: 100
System.out.println(buffer.getInt(0));
System.out.println(copied.getInt(0));
// 복제 버퍼의 값 수정
copied.setInt(0, 300);
// buffer: 200 & copied: 300
System.out.println(buffer.getInt(0));
System.out.println(copied.getInt(0));
slice()는 원본 버퍼의 특정 바이트 배열을 다른 버퍼에 복사합니다.
이때 데이터가 복사되는 방식은 참조에 의한 복사 | 깊은 복사(Deep copy)입니다.
Buffer buffer = Buffer.buffer();
buffer.appendInt(100);
// 버퍼를 slice() 메소드로 복사
Buffer sliced = buffer.slice();
// buffer: 100 & sliced: 100
System.out.println(buffer.getInt(0));
System.out.println(sliced.getInt(0));
// 원래 버퍼의 값 수정
buffer.setInt(0, 200);
// buffer: 200 & sliced: 200
System.out.println(buffer.getInt(0));
System.out.println(sliced.getInt(0));
// 복제 버퍼의 값 수정
sliced.setInt(0, 300);
// buffer: 300 & sliced: 300
System.out.println(buffer.getInt(0));
System.out.println(sliced.getInt(0));
버퍼 재사용
버퍼는 소켓 등으로 Write한 이후 버퍼를 재사용 할 수 없습니다.
버퍼를 소켓으로 Write하는 과정:send() 등은 비동기로 처리되므로 버퍼가 스트리밍 되는 과정에서 재사용으로 인해 값이 변경 될 수 있습니다.
정리 및 복습
- 버퍼는 바이트의 연속이며 Write 과정에서
자동으로 크기가 확장될 수 있습니다. - 이로 인해
Write 과정에서 IndexOutOfBoundsException 오류가 발생하지 않는 특징이 있습니다. 부호가 없는(unsigned) 데이터 유형을 사용하면 스트리밍을 위한버퍼의 크기를 최적화할 수 있습니다.- 버퍼를 복사하기 위한
copy(): 값에 의한 복사 | 얕은 복사(Shallow copy)이며slice(): 참조에 의한 복사 | 깊은 복사(Deep copy)입니다. - 버퍼는 스트리밍 과정에서 값이 변경되면 위험하므로
재사용을 지양합니다.
'Java > Vert.x' 카테고리의 다른 글
버텍스 코어: TCP 클라이언트 (0) | 2022.03.12 |
---|---|
버텍스 코어: TCP 서버 (0) | 2022.03.07 |
메시지 코덱(Message codecs)을 구현하여 이벤트 버스에서 사용자 메시지 주고 받기 (0) | 2022.03.02 |
버텍스 코어: 메시지 코덱(Message codecs) (0) | 2022.03.02 |
버텍스 코어: 이벤트 버스(Event Bus) (0) | 2022.02.01 |