자바스크립트 인코딩과 버퍼

서건혁·2024년 3월 9일
2
post-thumbnail

Node.js에서 버퍼는 바이너리 데이터를 조작하기 위해서 존재한다. 하지만 우리는 그것이 어떻게 작동되는지 잘 모른다. 컴퓨터는 모든 타입의 데이터를 이진수인 1과 0으로 표현한다. 숫자를 저장하기 위해서 컴퓨터는 먼저 그것을 바이너리 데이터 형식으로 바꾼다. 그리고 이러한 방식은 매우 직관적으로 이루어진다.

이러한 변환은 예시에서 든 숫자 타입 뿐만 아니라 이미지, 텍스트, 비디오 모두 마찬가지로 바이너리 데이터로 변환된다. 그러나 그러한 데이터가 바이너리 데이터로 표현되기 위해서는 우리는 이러한 타입들이 어떻게 바이너리 데이터로 변환될지 규칙을 지정해야 한다.

여기에는 다양한 규칙이 있는데, 이것을 인코딩 방식이라고 하며 인코딩 방식을 통해 해당 데이터가 바이너리 데이터로 어떻게 표현될지 정해진다.

다시 말해, 모든 영상이나, 소리들은 일차적으로 다양한 인코딩 방식을 거쳐 알맞는 바이너리 데이터 형식으로 만들어져 저장된다. 각 인코딩 방식은 데이터 타입에 따라 달라지며 그 중 문자 인코딩 방식 중 가장 대표적인 방식은 UTF-8이 있다. 자바스크립트는 UTF-8 문자 인코딩 방식을 기본으로 채택한다. 이걸 설명하는 이유는 버퍼의 저장과 관련이 있기 때문이다.

버퍼는 숫자로된 메모리 덩어리이자 숫자로 구성된 배열과 비슷하다. 우리는 이러한 버퍼를 마음대로 할당할 수 있다. 하지만 이미 할당한 버퍼의 크기를 마음대로 변경할 수는 없다.

또한 버퍼는 바이트로 이루어진 배열이다. 그래서 버퍼 원소에 할당할 수 있는 최대 숫자는 0부터 255까지밖에 할당할 수 없다.(2^8 = 256, 0 ~ 255 = 256가지)


const buffer = new Buffer(5);
 
buffer[0] = 255;
console.log(buffer[0]); // 255
 
buffer[1] = 256; //256이상일 경우 이를 256으로 나눈 나머지 값이 된다.
console.log(buffer[1]); // 0
 
buffer[2] = 260;
console.log(buffer[2]); // 4
console.log(buffer[2] === 260%256); // true
 
buffer[3] = 516; 
console.log(buffer[3]); // 4
console.log(buffer[3] === 516%256); // true
 
buffer[4] = -50; //음수는 표현할 수 없다.
console.log(buffer[4]); // 2의 보수법이 적용되어 206이 출력된다.

버퍼에는 숫자 뿐만 아니라 문자열도 저장할 수 있다.(숫자로 이루어진 배열에 문자열을 저장할 수 있는 이유는 위에서 설명했다.)


const buffer = Buffer.from('Hello world!');

이러한 문자열을 인코딩 할 때는 기본적으로 UTF-8 문자 인코딩 방식이 적용되며 2번째 인수를 추가함으로써 문자 인코딩 방식을 바꿀 수 있다.

const buffer = Buffer.from('Hello world!', 문자 인코딩 방식);
//문자열이 해당 인코딩 방식으로 정의된다.

버퍼에 저장한 문자열은 바로 문자열로 출력될 수는 없으며 이 과정에서 toString() 함수를 통해서 문자열로 바꿔야 한다.


const buffer = Buffer.from('Hello world!');
 
console.log(buffer.toString()); // Hello world!

console.log(Buffer.from(’Hello world’));
명령어를 입력했을 때 출력되는 숫자는 UTF-8로 인코딩한 바이너리 데이터이다.
이를 통해 buffer에 저장되는 문자열은 문자 인코딩 방식을 통해 새롭게 저장된다는 것을 알 수 있다.

버퍼에는 문자열 뿐만 아니라 이모지도 저장할 수 있는데, 이러한 이모지는 여러개의 바이트 코드로 이루어진 경우가 있다. 문제는 어떠한 경우에는 toString() 함수를 사용해서 버퍼의 이모지를 출력할 수 없다.

//Hello 🌎 world! 출력하고 싶을 때
const buffers = [
  Buffer.from('Hello '),
  Buffer.from([0b11110000, 0b10011111]), //이모지 바이트코드
  Buffer.from([0b10001100, 0b10001110]), //이모지 바이트코드
  Buffer.from(' world!'),
];

let result = '';
buffers.forEach((buffer) => {
  result += buffer.toString();
});
//예상과는 다른 결과가 나온다.
console.log(result); // Hello ��� world!

😁알아두면 좋은 자바스크립트 지식
이모지의 바이트코드를 쓸 때 앞에 붙어있는 0b는 자바스크립트에서 이것이 바이너리 데이터를 의미한다는 것을 나타낸다.

이러한 결과가 나오는 이유는 전체적으로 이모지의 바이트 코드는 총 4개인데, buffers 배열에서 원소를 한 개씩만 꺼내어 그것을 toString() 함수를 통해 변환을 시도하기 때문이다.

즉 실제로 변환되려면 Buffer.from([0b11110000, 0b10011111, 0b10001100, 0b10001110]).toString();을 써야 하는 것이다.

이러한 경우에 분리된 바이트코드를 합쳐서 정상적인 이모지가 출력되게 하려면 어떻게 해야 할까?

그럴 때 StringDecoder를 사용할 수 있다. StringDecoder는 바이트 코드를 통해 정상적인 문자열이 나오지 않는다면, 정상적인 문자열이 완성될 수 있을 때까지 다음 데이터를 받아서 그것을 합칠 수 있도록 기존 바이트코드를 유보시킨다.


import { StringDecoder } from 'string_decoder';
 
const decoder = new StringDecoder('utf8');
 
const buffers = [
  Buffer.from('Hello '),
  Buffer.from([0b11110000, 0b10011111]), // [0b11110000, 0b10011111]는 이 자체만으로는 잘못된 문자열이다.
  Buffer.from([0b10001100, 0b10001110]), // StringDecoder는 해당 문자열을 정상적인 문자열로 만들기 위해 이전에 받았던 문자열을 유보시키고 다음 바이트코드를 통해 정상적인 문자열이 완성될 수 있도록 한다.
  Buffer.from(' world!'),
];
//reduce는 반복해서 나온 값을 누적해서 합치는 역할을 수행한다.(result에 결과 누적)
const result = buffers.reduce((result, buffer) => (
  `${result}${decoder.write(buffer)}` 
), '');
 
console.log(result); // Hello 🌎 world!

또한 버퍼로 이루어진 파일을 읽을 수도 있다.

import * as fs from 'fs';
import * as util from 'util'
 
const readFile = util.promisify(fs.readFile);
 
readFile('./file.txt') //해당 txt 파일은 버퍼의 문자 인코딩 방식에 맞는 데이터가 쓰여있다고 가정.
  .then((content) => {
    console.log(content instanceof Buffer); // true
    console.log(content.toString())
  })
  .catch(error => console.log(error));

출처

Node.js TypeScript #3 Explaining the Buffer

0개의 댓글