Buffer

Log. Tue, Nov 22, 2016 11:52 AM | Category : Node.js | Views: 191

 Node.js의 버퍼는 노드에서 바이너리 데이터를 다룰 수 있게 해주는 객체입니다. 이는 ES6의 TypedArray와 ArrayBuffer와도 중첩이 되는데 사실 Node가 등장했을 당시에는 저러한 스펙이 없었습니다. 하지만 노드는 내부적으로 바이너리 데이터를 처리할 수 있는 기술이 필요했고 그를 위한 기술이 바로 Buffer 객체입니다.

 

Node.js 6.x부터 Buffer객체를 new를 사용해 생성자로 객체를 생성하는 것이 deprecated되었습니다.

var buffer = new Buffer(10);    // Deprecated!

 

대신 새롭게 제공하는 여러 메서드들을 이용해 버퍼를 선언합니다.

var buffer = Buffer.alloc(10);     // 0으로 초기화된 10바이트의 버퍼를 생성

 

버퍼는 기본적으로 0이 채워져서 만들어지지만 allocUnsafe로 생성하면 초기화되지 않은 버퍼를 만들 수 있습니다. 이때 버퍼 내부에는 일명 쓰레기 값이라는 데이터가 들어있는데 이는 이전에 프로그램들이 사용한 데이터들입니다. 초기화되지 않은 버퍼는 초기화되는 버퍼보다 속도가 빠르기 때문에 유용하게 사용될 수 있습니다.

var buffer = new Buffer.allocUnsafe(10);   // 초기화되지 않은 10바이트 버퍼 생성

 

alloc은 버퍼를 만들고 초기화하지만 from 메서드를 사용하면 존재하는 데이터를 기반으로 버퍼를 생성할 수 있습니다.

// 배열로 버퍼 생성
var buf1 = Buffer.from([1, 2, 3]);
console.log(buf1);	// <Buffer 01 02 03>


// 문자열로 버퍼 생성
var buf2 = Buffer.from('Hello');
console.log(buf2.toString());	// Hello


// ArrayBuffer로 버퍼 생성
var uint8Array = new Uint8Array(4);
uint8Array[0] = 5000;
uint8Array[1] = 4000;
var buf3 = Buffer.from(uint8Array.buffer);
console.log(buf3);	// <Buffer 88 a0 00 00>


// 배열 버퍼로 생성한 버퍼는 레퍼런스.
// 따라서 TypedArray를 수정하면 버퍼도 변경된다.
uint8Array[2] = 3000;
console.log(buf3);	// <Buffer 88 a0 b8 00>


// 버퍼로 버퍼 생성
var tBuf = Buffer.alloc(10);
tBuf[0] = 0x01;
var buf4 = Buffer.from(tBuf);
console.log(tBuf);  // <Buffer 01 00 00 00 00 00 00 00 00 00>

 

버퍼는 내부적으로 length를 가지고 있는데 이는 버퍼의 크기를 뜻합니다. 버퍼는 각각이 1바이트를 의미하므로 length가 4면 4바이트, 10이면 10바이트가 됩니다.

var buf4 = new Buffer([0x01, 0x02]);
console.log(buf4.length);    // 2 = 2 bytes

 

여러 버퍼를 하나로 결합할 땐 concat 메서드를 쓰면 됩니다.

var buf1 = Buffer.from([0x01]);
var buf2 = Buffer.from([0x02]);
var buf = Buffer.concat([buf1, buf2]);
console.log(buf);	// <Buffer 01 02>

 

concat은 두 번째 인자로 총 바이트 크기를 명시할 수 있습니다. 이를 이용하면 불필요한 바이트를 제외할 수 있습니다.

var buf1 = Buffer.alloc(10);
var buf2 = Buffer.alloc(10);
var buf = Buffer.concat([buf1, buf2], 15);
console.log(buf.length);    // 15

 

버퍼의 각 칸은 1바이트의 데이터를 포함할 수 있습니다. 따라서 0부터 255(= 0x00 ~ 0xFF)까지의 데이터를 보관할 수 있습니다.

var buf = Buffer.from([0x01, 0xFF]);
console.log(buf);    // <Buffer 01 ff>

 

배열을 한번에 같은 데이터로 설정하고 싶을 경우 fill 메서드를 사용합니다.

var buf = Buffer.allocUnsafe(10).fill(0);
console.log(buf);    // <Buffer 00 00 00 00 00 00 00 00 00 00>

 

버퍼에서 원하는 범위의 데이터를 복제하고 싶은 경우 copy메서드를 사용하면 됩니다.

var buf1 = Buffer.allocUnsafe(20).fill(1);
var buf2 = Buffer.alloc(20);

buf1.copy(buf2, 0, 10, 12);
console.log(buf2);
// <Buffer 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>

 

buffer는 write라는 메서드를 제공하는데, 이는 버퍼에 문자열 데이터를 쓰는 메서드입니다(비슷한 기능을 하는 것으로 앞선 예제에서 일부 사용된 배열 첨자([])가 있는데, 이는 버퍼의 각 바이트 위치에 직접적으로 엑세스 할 수 있게 해줍니다).

write메서드는 첫번째 인자로 전달한 문자열을 기록하며 이후 인자들은 전부 옵션입니다. 순서대로 바이트 오프셋, 쓸 바이트 수, 인코딩이며 기본 인코딩은 utf8을 사용합니다. 또한 함수의 반환 값으로 기록한 바이트 값을 전달합니다.

var buf = Buffer.alloc(30);
var len = buf.write('Hello, .modernator!');

console.log(buf);
// <Buffer 48 65 6c 6c 6f 2c 20 2e 6d 6f 64 65 72 6e 61 74 6f 72 21 00 00 00 00 00 00 00 00 00 00 00>
console.log(buf.toString());
// Hello, .modernator!

 

buffer에 toString을 사용하면 버퍼의 데이터를 문자열로 반환합니다. 버퍼는 기본적으로 문자열을 쓸 경우 utf8으로 기록되며, 마찬가지 toString으로 불러올 때에도 utf8으로 불러옵니다. 만약 특정 인코딩을 지정하고 싶다면 toString의 인자로 인코딩을 전달하면 됩니다.

var buf = Buffer.alloc(10);
buf[0] = 72;
buf[1] = 69;
buf[2] = 76;
buf[3] = 76;
buf[4] = 79;

console.log(buf.toString('ascii')); // HELLO

 

 

buffer는 기본적으로 unsigned int로 1바이트를 읽어오지만 그보다 더 큰 데이터를 읽을 수 있는 메서드들을 제공합니다.

  • readDoubleBE
  • readDoubleLE
  • readFloatBE
  • readFloatLE
  • readInt8
  • readInt16BE
  • readInt16LE
  • readInt32BE
  • readInt32LE
  • readIntBE
  • readIntLE
  • readUint8
  • readUint16BE
  • readUint16LE
  • readUint32BE
  • readUint32LE
  • readUintBE
  • readUintLE

 

각각의 메서드들은 이름만 봐서 대강 기능을 유추하실 수 있습니다. 이를 정리하면 아래와 같습니다.

BE LE
빅 엔디안 리틀 엔디안

 

Double Float Int Int16 Int32 Uint8 Uint16 Uint32
64비트 부동소숫점 32비트 부동소숫점 8비트 부호있는 정수 16비트 부호있는 정수 16비트 부호있는 정수 8비트 부호없는 정수 16비트 부호없는 정수 32비트 부호없는 정수

 

따라서 이를 조합하며, 대부분의 사용법은 동일합니다.

var buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
console.log(buf.readDoubleBE());    // 8.20788039913184e-304
console.log(buf.readDoubleLE());    // 5.447603722011605e-270

 

각각은 인자로 오프셋을 전달하는데 주의할점은 Double은 반드시 버퍼의 길이가 8보다 커야하고(8바이트 = 64비트 = Double Float), Float는 반드시 버퍼 길이가 4보다 커야한다는 점 입니다(4바이트 = 32비트 = Float). 만약 그렇지 않을 경우 예외가 발생하고 RangeError가 전달됩니다. 그외 각각의 이름이 요구하는 바이트수보다 작은 경우도 모두 마찬가지로 예외가 발생하니 주의해야 합니다.

위 메서드들 중 아래 메서드는 사용방법이 조금 다릅니다.

  • readIntBE / readIntLE
  • readUintBE / readUintLE

 

각각이 의미하는 바는 위에 표들과 동일하나 8/16/32가 아닌 사용할 바이트 수를 명시할 수 있습니다. 순서는 바이트 오프셋, 바이트 크기 순입니다.

var buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]);
var read6Bytes = buf.readIntLE(0, 6);

 

read가 있다면 반대로 write도 있겠죠? 네, 마찬가지로 각각에 대응하는 write함수 또한 제공됩니다.

  • writeDoubleBE
  • writeDoubleLE
  • writeFloatBE
  • writeFloatLE
  • writeInt8
  • writeInt16BE
  • writeInt16LE
  • writeInt32BE
  • writeInt32LE
  • writeUint8
  • writeUint16BE
  • writeUint16LE
  • writeUint32BE
  • writeUint32LE
  • writeUintBE
  • writeUintLE

 

read함수들은 인자가 오프셋을 전달하며 write함수는 인자로 설정할 값, 바이트 오프셋을 전달합니다. read의 오프셋은 생략이 가능한데 write도 오프셋 생략은 가능합니다.

var buf = Buffer.allocUnsafe(8);
buf.writeDoubleBE(0xdeadbeefcafebabe, 0);

console.log(buf);    // <Buffer 43 eb d5 b7 dd f9 5f d7>

 

또한 read처럼 writeIntBE, writeIntLE, writeUintBE, writeUintLE는 원하는 바이트 크기만큼을 쓸 수 있게 해줍니다.

var buf = Buffer.allocUnsafe(6);
buf.writeUIntBE(0x1234567890ab, 0, 6);

console.log(buf);    // <Buffer 12 34 56 78 90 ab>

 

버퍼를 쓰다보면 바이터 순으로 버퍼를 변경해야 하는 경우가 있는데 아래 메서드들은 이들을 지원합니다.

  • swap16
  • swap32
  • swap64

 

각각의 swap 메서드들은 버퍼를 각각에 해당하는 부호없는 정수로 평가하고 바이트 순서대로 정렬합니다.

var buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
console.log(buf1);    // <Buffer 01 02 03 04 05 06 07 08>

buf1.swap16();  // 16비트(2바이트)씩 바이트 순서대로 정렬

console.log(buf1);    // <Buffer 02 01 04 03 06 05 08 07>

 

마지막으로 buffer에서 제공하는 slice 메서드에 대해 알아보겠습니다. 이는 JavaScript의 Array.prototype.slice와 동일합니다. 이 메서드는 버퍼의 일부분을 추출하는데 사용할 수 있으며 인자로 범위를 지정합니다.

var buf1 = Buffer.alloc(10);
for(var i = 1; i < 10; i+=2) {
	buf1[i] = 1; 
}
console.log(buf1);    // <Buffer 00 01 00 01 00 01 00 01 00 01>

var buf2 = buf1.slice();	// copy whole Buffer
console.log(buf2);    // <Buffer 00 01 00 01 00 01 00 01 00 01>

var buf3 = buf1.slice(0, 5);
console.log(buf3);    // <Buffer 00 01 00 01 00>

 

또한 배열과 마찬가지로, slice로 복제된 버퍼는 참조값이므로 복제된 버퍼를 수정하면 원본 버퍼도 수정됩니다.

var buf1 = Buffer.alloc(10);
for(var i = 1; i < 10; i+=2) {
	buf1[i] = 1; 
}

var buf2 = buf1.slice();	// copy whole Buffer

buf2[0] = 1;
console.log(buf1);    // <Buffer 01 01 00 01 00 01 00 01 00 01>
console.log(buf2);    // <Buffer 01 01 00 01 00 01 00 01 00 01>

 

 

이상으로, Node.js의 버퍼에 대해 자세히 살펴보았습니다. 제가 정리한 예제와 기능들은 버퍼 객체에서 제공하는 대부분의 핵심 기능입니다. 하지만 그외에도 Buffer는 많은 기능을 내장하고 있으니 각 동작에 대한 상세한 정보를 알고싶으시면 NodeJS 문서를 확인해주세요.