Node.js에서 버퍼 사용하기

저자는 COVID-19 구호 기금기부를 받을 단체로 선정했습니다.

소개

A buffer is a space in memory (typically RAM) that stores binary data. In Node.js, we can access these spaces of memory with the built-in Buffer class. Buffers store a sequence of integers, similar to an array in JavaScript. Unlike arrays, you cannot change the size of a buffer once it is created.

이미 Node.js 코드를 작성한 적이 있다면 암묵적으로 버퍼를 사용해 본 적이 있을 것입니다. 예를 들어, fs.readFile()로 파일을 읽을 때, 콜백 또는 Promise으로 반환되는 데이터는 버퍼 객체입니다. 또한, Node.js에서 HTTP 요청을 보낼 때, 클라이언트가 스트림을 한 번에 처리할 수 없는 경우에는 내부 버퍼에 일시적으로 저장된 데이터 스트림을 반환합니다.

버퍼는 일반적으로 하위 네트워킹 수준에서 바이너리 데이터와 상호 작용할 때 유용합니다. 또한 Node.js에서 세밀한 데이터 조작을 수행할 수 있는 능력을 제공합니다.

이 튜토리얼에서는 Node.js REPL을 사용하여 버퍼의 다양한 예제를 실행합니다. 버퍼 생성, 버퍼에서 읽기, 버퍼에 쓰기 및 복사, 그리고 이진 및 인코딩된 데이터 간의 변환에 버퍼 사용 등이 포함됩니다. 이 튜토리얼의 끝에는 이진 데이터를 처리하는 데 Buffer 클래스를 사용하는 방법을 배울 것입니다.

전제 조건

단계 1 — 버퍼 생성

이 첫 번째 단계에서는 Node.js에서 버퍼 객체를 생성하는 두 가지 기본 방법을 보여줍니다.

어떤 방법을 사용할지 결정하려면 다음 질문에 답해야합니다: 새 버퍼를 만들고 있습니까, 아니면 기존 데이터에서 버퍼를 추출하고 있습니까? 아직 수신하지 않은 메모리에 데이터를 저장할 예정이라면 새 버퍼를 만들어야합니다. Node.js에서는 이를 위해 Buffer 클래스의 alloc() 함수를 사용합니다.

자세히 보기 위해 Node.js REPL을 엽니다. 터미널에서 node 명령을 입력하십시오:

  1. node

프롬프트가 >로 시작되는 것을 볼 수 있습니다.

alloc() 함수는 버퍼의 크기를 첫 번째 및 유일한 필수 인수로 취합니다. 크기는 버퍼 객체가 사용할 메모리 바이트 수를 나타내는 정수입니다. 예를 들어, 1KB(킬로바이트) 크기의 버퍼를 만들고 싶다면 콘솔에 다음을 입력하면 됩니다:

  1. const firstBuf = Buffer.alloc(1024);

새로운 버퍼를 생성하기 위해 전역으로 사용 가능한 Buffer 클래스를 사용했으며, 이 클래스에는 alloc() 메서드가 있습니다. alloc()1024를 인수로 제공하여 1KB 크기의 버퍼를 생성했습니다.

기본적으로 alloc()을 사용하여 버퍼를 초기화할 때 나중에 데이터를 위한 자리 표시자로 이진 0으로 채워집니다. 그러나 원하는 경우 기본 값을 변경할 수 있습니다. 만약 0 대신 1로 채워진 새로운 버퍼를 만들고 싶다면, alloc() 함수의 두 번째 매개변수인 fill을 설정합니다.

터미널에서 REPL 프롬프트에서 1로 채워진 새로운 버퍼를 생성하세요:

  1. const filledBuf = Buffer.alloc(1024, 1);

우리는 방금 메모리에 1KB의 1을 저장하는 새로운 버퍼 객체를 생성했습니다. 정수를 입력했지만, 버퍼에 저장된 모든 데이터는 이진 데이터입니다.

이진 데이터는 여러 가지 형식으로 나타날 수 있습니다. 예를 들어, 바이트 데이터를 나타내는 바이너리 시퀀스를 고려해 봅시다: 01110110. 이진 시퀀스가 영어 문자열을 나타내고 있다면 ASCII 인코딩 표준을 사용하면 문자 v가 됩니다. 그러나 컴퓨터가 이미지를 처리하는 경우 이진 시퀀스는 픽셀의 색상에 대한 정보를 포함할 수 있습니다.ASCII 인코딩 표준

컴퓨터는 바이트가 다른 방식으로 인코딩되어 있기 때문에 그것들을 다르게 처리할 수 있습니다. 바이트 인코딩은 바이트의 형식입니다. 노드.js의 버퍼는 문자열 데이터로 초기화되었다면 기본적으로 UTF-8 인코딩 체계를 사용합니다. UTF-8의 바이트는 숫자, 영어 및 다른 언어의 문자 또는 기호를 나타냅니다. UTF-8은 정보 교환용 미국 표준 코드 (ASCII)의 슈퍼셋입니다. ASCII는 대문자 및 소문자 영어 알파벳, 숫자 0-9 및 느낌표 (!) 또는 앰퍼샌드 기호 (&)와 같은 몇 가지 다른 기호를 바이트로 인코딩할 수 있습니다.

ASCII 문자만 처리할 수 있는 프로그램을 작성한다면 버퍼에서 사용되는 인코딩을 alloc() 함수의 세 번째 인자인 encoding을 사용하여 변경할 수 있습니다.

다음은 다섯 바이트로 이루어진 새로운 버퍼를 만들어 ASCII 문자만 저장하는 예입니다:

  1. const asciiBuf = Buffer.alloc(5, 'a', 'ascii');

이 버퍼는 ASCII 표현을 사용하여 문자 a의 다섯 바이트로 초기화됩니다.

참고: 기본적으로 노드.js는 다음과 같은 문자 인코딩을 지원합니다:

  • ASCII, ascii로 표시됨
  • UTF-8, utf-8 또는 utf8로 표시됨
  • UTF-16, utf-16le 또는 utf16le로 표시됨
  • UCS-2ucs-2 또는 ucs2로 표시됩니다.
  • Base64base64로 표시됩니다.
  • 16진수hex로 표시됩니다.
  • ISO/IEC 8859-1latin1 또는 binary로 표시됩니다.

모든 이러한 값은 encoding 매개변수를 허용하는 Buffer 클래스 함수에서 사용할 수 있습니다. 따라서 이러한 값은 모두 alloc() 메서드에 유효합니다.

지금까지 alloc() 함수로 새 버퍼를 생성했습니다. 그러나 때로는 문자열이나 배열과 같이 이미 존재하는 데이터에서 버퍼를 만들고 싶을 수 있습니다.

기존 데이터에서 버퍼를 만들려면 from() 메서드를 사용합니다. 다음과 같은 데이터에서 버퍼를 만들 수 있습니다:

  • 정수 배열: 정수 값은 0에서 255 사이여야 합니다.
  • ArrayBuffer: 이는 바이트의 고정 길이를 저장하는 JavaScript 객체입니다.
  • A string.
  • 다른 버퍼.
  • 다른 JavaScript 객체들도 Symbol.toPrimitive 속성을 가지고 있습니다. 이 속성은 객체를 기본 데이터 유형으로 변환하는 방법을 JavaScript에 알려줍니다: boolean, null, undefined, number, string, 또는 symbol. Symbols에 대해 더 알아보려면 Mozilla의 JavaScript 문서를 참조하세요.

이제 문자열에서 버퍼를 생성하는 방법을 살펴보겠습니다. Node.js 프롬프트에서 다음을 입력하십시오:

  1. const stringBuf = Buffer.from('My name is Paul');

이제 문자열 My name is Paul에서 생성된 버퍼 객체가 있습니다. 이전에 만든 다른 버퍼에서 새 버퍼를 만들어 봅시다:

  1. const asciiCopy = Buffer.from(asciiBuf);

이제 asciiBuf와 동일한 데이터를 포함하는 새로운 버퍼 asciiCopy가 생성되었습니다.

이제 버퍼를 생성하는 경험이 있으므로, 데이터를 읽는 예제로 진입해보겠습니다.

단계 2 — 버퍼에서 읽기

버퍼에서 데이터에 액세스하는 방법은 여러 가지가 있습니다. 버퍼에서 개별 바이트에 액세스하거나 전체 내용을 추출할 수 있습니다.

버퍼의 하나의 바이트에 액세스하려면, 우리는 원하는 바이트의 인덱스나 위치를 전달합니다. 버퍼는 배열처럼 데이터를 순차적으로 저장합니다. 또한 배열처럼 데이터를 인덱싱하며, 0부터 시작합니다. 개별 바이트를 얻으려면 버퍼 객체에 배열 표기법을 사용할 수 있습니다.

이것이 REPL에서 문자열을 기반으로 버퍼를 생성하여 어떻게 보이는지 살펴보겠습니다:

  1. const hiBuf = Buffer.from('Hi!');

이제 버퍼의 첫 번째 바이트를 읽어 봅시다:

  1. hiBuf[0];

ENTER를 누르면 REPL에서 다음이 표시됩니다:

Output
72

정수 72는 글자 H의 UTF-8 표현에 해당합니다.

참고: 바이트의 값은 0에서 255 사이의 숫자일 수 있습니다. 바이트는 8개의 비트로 구성됩니다. 비트는 이진이므로 0 또는 1 중 하나의 값만 가질 수 있습니다. 8개의 비트의 시퀀스가 있고 각 비트 당 두 가지 가능한 값이 있다면, 바이트에 대한 가능한 값은 최대 2⁸입니다. 이것은 최대 256개의 값으로 나타납니다. 우리는 0부터 카운트하기 때문에, 최대 숫자는 255입니다.

이제 두 번째 바이트에 대해 동일한 작업을 수행해 보겠습니다. REPL에 다음을 입력하십시오:

  1. hiBuf[1];

REPL은 소문자 i를 나타내는 105를 반환합니다.

마지막으로 세 번째 문자를 가져와 봅시다:

  1. hiBuf[2];

REPL에서 33이 표시됩니다. 이는 !에 해당합니다.

유효하지 않은 인덱스에서 바이트를 검색해 보겠습니다:

  1. hiBuf[3];

REPL은 다음을 반환합니다:

Output
undefined

이는 배열에서 잘못된 인덱스로 요소에 액세스하려고 시도한 것과 마찬가지입니다.

이제 버퍼의 모든 데이터를 한 번에 검색하는 옵션을 살펴보겠습니다. 버퍼 객체에는 전체 내용을 두 가지 다른 형식으로 반환하는 toString()toJSON() 메서드가 있습니다.

그 이름에서 알 수 있듯이 toString() 메서드는 버퍼의 바이트를 문자열로 변환하여 사용자에게 반환합니다. 이 메서드를 hiBuf에 사용하면 문자열 Hi!을 얻게 됩니다. 한번 시도해 보죠!

프롬프트에서 다음을 입력하십시오:

  1. hiBuf.toString();

REPL은 다음을 반환합니다:

Output
'Hi!'

그 버퍼는 문자열에서 생성되었습니다. 이번에는 문자열 데이터가 아닌 버퍼에서 toString()을 사용할 경우 어떻게 되는지 살펴보겠습니다.

빈 버퍼를 생성하고 크기를 10 바이트로 지정해 보겠습니다:

  1. const tenZeroes = Buffer.alloc(10);

이제 toString() 메서드를 사용해 보겠습니다:

  1. tenZeroes.toString();

다음 결과를 볼 수 있습니다:

'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'

문자열 \u0000NULL에 대한 유니코드 문자입니다. 이것은 숫자 0에 해당합니다. 버퍼의 데이터가 문자열로 인코딩되지 않은 경우, toString() 메서드는 바이트의 UTF-8 인코딩을 반환합니다.

toString()은 선택적 매개변수인 encoding을 가지고 있습니다. 이 매개변수를 사용하여 반환되는 버퍼 데이터의 인코딩을 변경할 수 있습니다.

예를 들어, hiBuf의 16진수 인코딩을 원하는 경우 다음을 프롬프트에 입력하면 됩니다:

  1. hiBuf.toString('hex');

해당 문장은 다음으로 평가됩니다:

Output
'486921'

486921은 문자열 Hi!를 나타내는 바이트의 16진수 표현입니다. Node.js에서 사용자가 데이터의 인코딩을 한 형식에서 다른 형식으로 변환하려고 할 때, 일반적으로 문자열을 버퍼에 넣고 원하는 인코딩으로 toString()을 호출합니다.

toJSON() 메서드의 동작이 다릅니다. 버퍼가 문자열에서 만들어졌는지 여부에 관계없이 항상 바이트의 정수 표현으로 데이터를 반환합니다.

hiBuftenZeroes 버퍼를 재사용하여 toJSON()을 사용하는 연습을 해보겠습니다. 프롬프트에 다음을 입력하십시오:

  1. hiBuf.toJSON();

REPL은 다음을 반환합니다:

Output
{ type: 'Buffer', data: [ 72, 105, 33 ] }

JSON 객체에는 항상 Buffertype 속성이 있습니다. 이렇게 함으로써 프로그램이 이러한 JSON 객체를 다른 JSON 객체와 구별할 수 있습니다.

data 속성에는 바이트의 정수 표현 배열이 포함되어 있습니다. 개별적으로 바이트를 가져올 때 받은 값들에 해당하는 72, 105, 및 33를 주목하실 수 있을 겁니다.

tenZeroes와 함께 toJSON() 메서드를 시도해 봅시다:

  1. tenZeroes.toJSON();

REPL에서 다음을 볼 수 있습니다:

Output
{ type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }

type은 이전에 언급한 것과 동일합니다. 그러나 데이터는 이제 열 개의 제로로 구성된 배열입니다.

이제 버퍼에서 읽는 주요 방법을 다루었으니, 버퍼의 내용을 수정하는 방법을 살펴보겠습니다.

단계 3 — 버퍼 수정

기존 버퍼 객체를 수정하는 여러 가지 방법이 있습니다. 읽는 것과 마찬가지로 배열 구문을 사용하여 버퍼 바이트를 개별적으로 수정할 수 있습니다. 또한 기존 데이터를 대체하여 새로운 내용을 버퍼에 쓸 수도 있습니다.

시작하기 전에 버퍼의 개별 바이트를 어떻게 변경할 수 있는지 살펴보겠습니다. 우리가 기억해야 할 버퍼 변수 hiBuf는 문자열 Hi!를 포함하고 있습니다. 각 바이트를 Hey로 변경해 봅시다.

REPL에서 먼저 hiBuf의 두 번째 요소를 e로 설정해 보겠습니다:

  1. hiBuf[1] = 'e';

이제 이 버퍼를 문자열로 표시하여 올바른 데이터를 저장하는지 확인해 봅시다. toString() 메서드를 호출하여 확인합니다:

  1. hiBuf.toString();

이것은 다음과 같이 평가됩니다:

Output
'H\u0000!'

우리는 이상한 출력을 받았습니다. 왜냐하면 버퍼는 정수값만 허용하기 때문입니다. 우리는 문자 e를 할당할 수 없습니다. 대신, e를 나타내는 이진 동등체를 가진 숫자를 할당해야 합니다:

  1. hiBuf[1] = 101;

이제 toString() 메서드를 호출하면:

  1. hiBuf.toString();

REPL에서 이 출력을 받게 됩니다:

Output
'He!'

버퍼의 마지막 문자를 변경하려면, 세 번째 요소를 y의 바이트에 해당하는 정수로 설정해야 합니다:

  1. hiBuf[2] = 121;

다시 한 번 toString() 메서드를 사용하여 확인해 봅시다:

  1. hiBuf.toString();

당신의 REPL은 다음을 표시할 것입니다:

Output
'Hey'

만약 버퍼의 범위를 벗어난 바이트를 쓰려고 하면, 무시되고 버퍼의 내용은 변경되지 않습니다. 예를 들어, 버퍼의 존재하지 않는 네 번째 요소를 o로 설정해 보겠습니다:

  1. hiBuf[3] = 111;

toString() 메서드를 사용하여 버퍼가 변경되지 않았는지 확인할 수 있습니다:

  1. hiBuf.toString();

출력은 여전히 이와 같습니다:

Output
'Hey'

전체 버퍼의 내용을 변경하려면 write() 메서드를 사용할 수 있습니다. write() 메서드는 버퍼의 내용을 대체할 문자열을 받습니다.

write() 메서드를 사용하여 hiBuf의 내용을 다시 Hi!로 변경해 봅시다. Node.js 셸에서 다음 명령을 입력하세요:

  1. hiBuf.write('Hi!');

write() 메서드는 REPL에서 3을 반환했습니다. 이는 세 개의 바이트 데이터를 작성했기 때문입니다. 각 문자는 한 바이트 크기를 가지며, 이 버퍼는 문자마다 한 바이트를 사용하는 UTF-8 인코딩을 사용합니다. 만약 버퍼가 문자당 최소 두 바이트를 사용하는 UTF-16 인코딩을 사용했다면 write() 함수는 6을 반환했을 것입니다.

이제 toString()을 사용하여 버퍼의 내용을 확인하세요:

  1. hiBuf.toString();

REPL에서는 다음을 출력합니다:

Output
'Hi!'

이는 각 요소를 바이트 단위로 변경하는 것보다 빠릅니다.

버퍼의 크기보다 더 많은 바이트를 작성하려고 시도하면 버퍼 개체는 맞는 바이트만 허용합니다. 설명하기 위해 세 개의 바이트를 저장하는 버퍼를 만들어 봅시다:

  1. const petBuf = Buffer.alloc(3);

이제 이것에 Cats를 작성하려고 시도해 봅시다:

  1. petBuf.write('Cats');

write() 호출이 평가될 때 REPL은 버퍼에 세 개의 바이트만 작성되었다는 것을 나타내는 3을 반환합니다. 이제 버퍼에 처음 세 개의 바이트가 포함되어 있는지 확인해 보세요:

  1. petBuf.toString();

REPL은 다음을 반환합니다:

Output
'Cat'

write() 함수는 바이트를 순차적으로 추가하므로 첫 세 바이트만 버퍼에 배치됩니다.

그에 대비하여, 4바이트를 저장하는 Buffer를 만들어보겠습니다:

  1. const petBuf2 = Buffer.alloc(4);

동일한 내용을 그것에 기록해보겠습니다:

  1. petBuf2.write('Cats');

그런 다음 원래의 내용보다 공간을 적게 차지하는 새로운 내용을 추가해보겠습니다:

  1. petBuf2.write('Hi');

버퍼는 0에서 시작하여 순차적으로 쓰기 때문에, 버퍼의 내용을 출력하면:

  1. petBuf2.toString();

우리는 다음과 같은 내용을 볼 수 있습니다:

Output
'Hits'

첫 두 글자가 덮어쓰여지지만, 나머지 버퍼는 그대로 유지됩니다.

가끔 우리가 기존 버퍼에 원하는 데이터가 문자열에 있지 않고 다른 버퍼 객체에 있다면 어떻게 할까요. 이런 경우에는 copy() 함수를 사용하여 버퍼가 저장하는 내용을 수정할 수 있습니다.

두 개의 새로운 버퍼를 만들어보겠습니다:

  1. const wordsBuf = Buffer.from('Banana Nananana');
  2. const catchphraseBuf = Buffer.from('Not sure Turtle!');

wordsBufcatchphraseBuf 버퍼 모두 문자열 데이터를 포함하고 있습니다. catchphraseBufNot sure Turtle! 대신 Nananana Turtle!로 수정하려고 합니다. wordsBuf에서 NanananacatchphraseBuf로 복사하기 위해 copy()를 사용할 것입니다.

한 버퍼에서 다른 버퍼로 데이터를 복사하려면 정보를 제공하는 버퍼에서 copy() 메서드를 사용해야 합니다. 따라서 wordsBuf에 복사할 문자열 데이터가 있으므로 다음과 같이 복사해야 합니다:

  1. wordsBuf.copy(catchphraseBuf);

이 경우의 target 매개변수는 catchphraseBuf 버퍼입니다.

그것을 REPL에 입력하면 15가 반환되어 15바이트가 쓰였음을 나타냅니다. 문자열 Nananana은 데이터의 8바이트만 사용하므로 우리는 즉시 우리의 복사가 의도한 대로 되지 않았음을 알 수 있습니다. toString() 메서드를 사용하여 catchphraseBuf의 내용을 확인합니다:

  1. catchphraseBuf.toString();

REPL이 반환하는 것은:

Output
'Banana Nananana!'

copy()가 기본적으로 wordsBuf의 전체 내용을 가져와 catchphraseBuf에 넣었습니다. 우리의 목표에 더 맞게 선택적으로 동작하려면 Nananana만 복사하면 됩니다. 계속하기 전에 catchphraseBuf의 원래 내용을 다시 작성해 봅시다:

  1. catchphraseBuf.write('Not sure Turtle!');

copy() 함수에는 다른 버퍼로 데이터를 복사하는 데 사용할 수 있는 몇 가지 추가 매개변수가 있습니다. 이 함수의 모든 매개변수 목록은 다음과 같습니다:

  • targetcopy()의 유일한 필수 매개변수입니다. 이전 사용 사례에서 보았듯이 복사할 버퍼입니다.
  • targetStart – 이는 복사를 시작해야 할 대상 버퍼의 바이트 인덱스입니다. 기본적으로 0으로 설정되어 있으므로 버퍼의 시작부터 데이터를 복사합니다.
  • sourceStart – 이는 복사를 시작해야 할 소스 버퍼의 바이트 인덱스입니다.
  • sourceEnd – 이는 복사를 중지해야 할 소스 버퍼의 바이트 인덱스입니다. 기본적으로 버퍼의 길이입니다.

그래서, wordsBuf에서 NanananacatchphraseBuf로 복사하려면, 우리의 target은 이전과 마찬가지로 catchphraseBuf여야 합니다. targetStartNanananacatchphraseBuf의 시작 부분에 나타나길 원하기 때문에 0이어야 합니다. sourceStartNanananawordsBuf에서 시작하는 인덱스인 7이어야 합니다. sourceEnd는 버퍼의 길이와 같은 값이어야 합니다.

REPL 프롬프트에서 wordsBuf의 내용을 다음과 같이 복사하십시오:

  1. wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length);

REPL은 8바이트가 쓰여졌음을 확인합니다. wordsBuf.lengthsourceEnd 매개변수의 값으로 사용되는 방식에 유의하십시오. 배열과 마찬가지로 length 속성은 버퍼의 크기를 제공합니다.

이제 catchphraseBuf의 내용을 확인해 봅시다:

  1. catchphraseBuf.toString();

REPL은 다음을 반환합니다:

Output
'Nananana Turtle!'

성공입니다! 우리는 wordsBuf의 내용을 복사하여 catchphraseBuf의 데이터를 수정할 수 있었습니다.

노드.js REPL을 종료하고 싶다면 종료할 수 있습니다. 그러나 종료하면 생성된 모든 변수가 더 이상 사용할 수 없음을 유의하십시오:

  1. .exit

결론

이 튜토리얼에서는 버퍼가 메모리에 고정 길이 할당으로 이진 데이터를 저장하는 것을 배웠습니다. 먼저 버퍼를 메모리의 크기를 정의하고 기존 데이터로 초기화하여 생성했습니다. 그런 다음 버퍼에서 데이터를 읽어 각각의 바이트를 검사하고 toString()toJSON() 메서드를 사용했습니다. 마지막으로 버퍼에 저장된 데이터를 수정하기 위해 각 바이트를 변경하고 write()copy() 메서드를 사용했습니다.

버퍼를 통해 이진 데이터가 Node.js에서 어떻게 조작되는지에 대한 통찰력을 얻을 수 있습니다. 이제 버퍼와 상호 작용할 수 있으므로 문자 인코딩이 데이터 저장에 어떻게 영향을 미치는지 다양한 방법을 관찰할 수 있습니다. 예를 들어, UTF-8 또는 ASCII 인코딩이 아닌 문자열 데이터에서 버퍼를 생성하고 크기의 차이를 관찰할 수 있습니다. 또한 UTF-8로 된 버퍼를 가져와 toString()을 사용하여 다른 인코딩 체계로 변환할 수 있습니다.

Node.js에서 버퍼에 대해 배우려면 Node.js 문서Buffer 개체를 읽어보십시오. Node.js를 계속 학습하고 싶다면 Node.js에서 코딩하는 방법 시리즈로 돌아가거나 Node 주제 페이지에서 프로그래밍 프로젝트 및 설정을 찾아보십시오.

Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js