[JS] Buffer

김현우·2022년 2월 22일
0

JavaScript

목록 보기
6/8

Buffer

원하는 크기의 메모리 공간을 할당받아 데이터를 저장하는 클래스

데이터를 1Byte씩 나누어 저장한다.

버퍼에는 데이터가 1Byte씩 나누어 저장된다는 점은 절대로 잊으면 안된다. 이를 인지하지 못하는 순간 버퍼의 동작을 이해할 수 없게된다.

보통 바이너리(이진) 데이터를 담아두기 위한 목적으로 사용된다.

형식화 배열 뷰의 일종인 Uint8Array의 하위클래스이지만 형식화 배열 뷰처럼 데이터를 직접 수정할 수 없다.

(링크) 형식화 배열이란

버퍼 생성 alloc

// 지정한 byte만큼의 메모리공간을 차지하는 버퍼를 생성한다.
const buf1 = Buffer.alloc(bytes)       // 해당 메모리 공간의 모든 값을 0으로 초기화한다
const buf2 = Buffer.allocUnsafe(bytes) // 0으로 초기화하지 않기 때문에 좀 더 빠르게 버퍼를 생성한다.

alloc을 통해 생성한 버퍼는 단지 메모리를 확보할 뿐이지 메모리에 데이터를 기록할 수 없다. 이 버퍼에 데이터를 쓰기 위해서는, 해당 버퍼를 바탕으로 형식화 배열 뷰를 만들어야 한다. 이는 형식화 배열 뷰에 대한 포스트에서 다루도록 한다.

버퍼 생성 Buffer.from

// (1)
const buf1 = Buffer.from("abc")
// (2)
const buf2 = Buffer.from([1, 100, 255])

지정한 값을 담는 버퍼를 생성한다. 딱 지정한 값이 가지는 byte만큼의 메모리가 버퍼에 할당된다.

(1) 문자열을 넣어 생성하면, 문자 하나당 1Byte의 크기를 가지기 때문에 3Byte만큼의 메모리를 할당받고, 각 바이트에는 a,b,c 문자를 나타내는 이진데이터가 기록된다.

(2) 배열을 넣으면 배열의 길이만큼의 바이트를 가지게 된다. 위에서는 배열에 요소가 3개이므로 3Byte를 할당받는다. 여기서 배열의 각 요소는 8bit 이진데이터를 10진수로 표현한 number이어야 한다. 배열을 인자로 버퍼를 생성하는 과정은 아래 링크로 달아 놓은 별도 페이지에서 설명하겠다.

Buffer.from(Array)

버퍼에 담긴 내용 파악하기

버퍼를 콘솔에 찍었을 때 나오는 내용을 파악해 보자.

cosnt buf1 = Buffer.from("abc")
console.log(buf1)
// <Buffer 61 62 63>

콘솔에 찍힌 61, 62, 63은 각각 a, b, c에 대응하는 아스키코드도 아니고 애초에 10진수도 아니다.

“abc”라는 문자열은 각 문자를 표현하는 비트(이진수)로 변환되어 버퍼에 담긴다. a,b,c 각 문자는 1Byte(8bit)를 넘기지 않는다. 따라서 콘솔에는 1Byte씩 3개가 찍힌다.

버퍼에는 해당 값의 이진데이터가 담긴다. 가령 ‘a’는 아스키코드로 97이다. 이 97이라는 10진수를 이진수로 바꾸면 ‘1100001’이다. 7bit로 ‘a’라는 문자를 표현할 수 있다는 것이다. 버퍼에 담기는 첫 1Byte는 ‘a’를 나타내는 ‘01100001’이다. 전체 버퍼를 상상하면 이런 모습이다.

<01100001 01100010 01100011>

버퍼에 담긴 각각의 값은 콘솔에서 16진수문자 두개로 표시된다. 한 개의 16진수는 비트 4개를 이용하여 0~15까지 표현한다.

‘a’를 예로 들어보자. 이진수로 바꾼 ‘a’는 ‘01100001’이다. 이 비트 8개를 4개씩 둘로 나누면 ‘0110’과 ‘0001’이다. 두 비트를 각각 16진수로 나타내면 ‘6’과 ‘1’이고, 이를 그대로 콘솔에 표시한 게 61이다.

인코딩과 디코딩

문자열을 버퍼에 담으면 그 버퍼에는 1Byte씩 여러 값이 들어있다. 이 때 각 문자를 비트로 어떻게 표현할 지에 대한 전략을 디코딩으로 볼 수 있다. 아스키 방식으로 ‘a’라는 문자를 디코딩하면 ‘01100001’ 즉 97이 나오고, “현우"방식으로 ‘a’라는 문자를 디코딩하면 ‘11110000’이 될 수도 있는 것이다.

인코딩이란, 버퍼에서 1Byte씩 읽으면서

각 바이트가 표현하는 10진수를 어떤 문자에 대응시킬지에 대한 전략이라고 생각하면 되겠다.

버퍼에 담긴 값 읽기

toString(encoding)

버퍼에 담긴 값을 문자열로 변환해 주는 메서드.

어떻게 인코딩하여 문자열을 만들 지는 인자로 넘겨주는데, 명시하지 않을 경우 기본 값인 utf8로 인코딩된다.

Buffer.prototype.values

버퍼에 담긴 바이트들을 이터러블하게 제공한다. (= for문에서 사용할 수 있다)

const buf = Buffer.from([16, 173, 159]) // <Buffer 10 ad 9f>

for (const data of buf.values()) {
	console.log(data) // 16, 173, 159 순으로 출력
}

console.log(buf.values()) // Object [Array Iterator] {}

여기서 주의할 점은, Buffer.prototype.values() 자체가 배열을 반환하지는 않는다는 것이다. 배열은 이터러블(반복가능)하지만, 모든 이터러블한 객체들이 배열은 아니다. 이해가 모호하다면 이터러블, 이터레이터에 관한 개념을 찾아보자.

Buffer.prototype.keys
Buffer.prototype.entries

keys() 메서드는 버퍼의 길이(인덱스)들을 이터러블하게 제공하는 메서드이고,

entries() 메서드는 keys와 values를 하나씩 담은 배열(length=2)을 제공한다.

for (const key of buf.keys()) console.log('key: ', key)
for (const value of buf.values()) console.log('value: ', value)
for (const entry of buf.entries()) console.log('entry: ', entry)

// key: 0
// key: 1
// key: 2
// value: 16
// value: 173
// value: 159
// entry: [0, 16]
// entry: [1, 173]
// entry: [2, 159]

버퍼를 다시 문자열로 만들기

문자열을 버퍼에 담으면 각 문자가 비트로 변환되어 담긴다고 했다. 이를 다시 문자열로 만들려면, 비트에서 1Byte씩 읽어서 문자로 변환하면 된다. 그런데 우리는 각 바이트가 나타내는 값(0-255)을 어떤 문자로 변환해야 할지, 변환한 문자들을 어떻게 배열하고 조합할지는 모른다. 그래서 항상 특정 인코딩방식을 따라 변환해야 한다. 위에서 언급한 toString() 메서드가 그 역할을 담당하는 셈이다. 덕분에 우리는 toString()에 원하는 인코딩방식을 명시하는 것만으로도 번거로운 작업을 피할 수 있다.

0개의 댓글