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クラスを使用する方法を学びます。

前提条件

  • 開発マシンにNode.jsがインストールされている必要があります。このチュートリアルではバージョン10.19.0を使用しています。macOSまたはUbuntu 18.04にこれをインストールするには、macOSでNode.jsをインストールしてローカル開発環境を作成する方法またはUbuntu 18.04にNode.jsをインストールする方法PPAを使用したインストールセクションの手順に従ってください。
  • このチュートリアルでは、Node.js REPL(Read-Evaluate-Print-Loop)でバッファとやり取りします。Node.js REPLの効果的な使用方法についての復習が必要な場合は、Node.js REPLの使用方法に関するガイドを参照してください。
  • この記事では、ユーザーが基本的なJavaScriptとそのデータ型に慣れていることを期待しています。それらの基本を学ぶことができるのは、私たちのJavaScriptでコーディングする方法シリーズです。

ステップ1 — バッファの作成

この最初のステップでは、Node.jsでバッファオブジェクトを作成する2つの主要な方法を紹介します。

どのメソッドを使用するかを決定するには、この質問に答える必要があります:新しいバッファを作成するか、既存のデータからバッファを抽出するか?まだ受け取っていないデータをメモリに格納する場合は、新しいバッファを作成する必要があります。Node.jsでは、これを行うためにBufferalloc()関数を使用します。

実際に確認するために、Node.js REPLを開いてみましょう。ターミナルで、nodeコマンドを入力してください。

  1. node

プロンプトが>で始まるのが見えるでしょう。

alloc()関数は、バッファのサイズを最初の必須引数として取ります。サイズは、バッファオブジェクトが使用するメモリのバイト数を表す整数です。例えば、1キロバイト(1024バイトに相当する)の大きさのバッファを作成したい場合は、コンソールにこれを入力します:

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

新しいバッファを作成するには、グローバルで利用可能なBufferクラスを使用しました。このクラスにはalloc()メソッドがあります。 alloc()に引数として1024を指定することで、1KBのバッファを作成しました。

alloc()でバッファを初期化すると、デフォルトで後でデータを置くためのバイナリゼロでバッファが埋められます。ただし、デフォルト値を変更することもできます。たとえば、0ではなく1で新しいバッファを作成したい場合は、alloc()関数の第2引数であるfillを設定します。

端末で、1で満たされた新しいバッファをREPLプロンプトで作成してください:

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

これで、1KBの1が格納されているメモリ内のスペースを参照する新しいバッファオブジェクトが作成されました。整数を入力しましたが、バッファに格納されるすべてのデータはバイナリデータです。

バイナリデータはさまざまな形式で提供されます。たとえば、1バイトのデータを表すバイナリシーケンスを考えてみましょう:01110110。このバイナリシーケンスが英語の文字列ASCIIエンコーディング規格を使用して表す場合、それは文字vになります。ただし、コンピューターが画像を処理している場合、このバイナリシーケンスにはピクセルの色に関する情報が含まれている可能性があります。

コンピューターは、バイトが異なる方法でエンコードされているため、それらを異なる方法で処理することを知っています。バイトのエンコードは、バイトの形式です。Node.jsのバッファは、文字列データで初期化された場合、デフォルトでUTF-8エンコーディングスキームを使用します。UTF-8の1バイトは、数字、英語や他の言語の文字、または記号を表します。UTF-8は、情報交換のためのアメリカ標準コードであるASCIIのスーパーセットです。ASCIIは、大文字と小文字の英字、数字0〜9、および感嘆符(!)やアンパサンド記号(&)などのいくつかの他の記号をバイトでエンコードできます。

ASCII文字のみで動作するプログラムを作成している場合、バッファで使用されるエンコーディングをalloc()関数の3番目の引数、encodingで変更できます。

ASCII文字のみを含む新しい5バイトのバッファを作成しましょう:

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

バッファは、ASCII表現を使用して、5バイトの文字aで初期化されています。

注意: デフォルトでは、Node.jsは以下の文字エンコーディングをサポートしています:

  • ASCIIasciiとして表されます
  • UTF-8utf-8またはutf8として表されます
  • UTF-16utf-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に伝えます:booleannullundefinednumberstring、またはsymbol。Symbolsについて詳しくは、MozillaのJavaScript documentationで読むことができます。

文字列からバッファを作成する方法を見てみましょう。Node.jsのプロンプトに、次のように入力します:

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

これで、文字列My name is Paulから作成されたバッファオブジェクトができました。以前に作成した別のバッファから新しいバッファを作成しましょう:

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

これで、asciiBufと同じデータを含む新しいバッファasciiCopyが作成されました。

バッファの作成を体験したので、そのデータを読み取る例に入ってみましょう。

ステップ2 — バッファからの読み取り

バッファ内のデータにアクセスする方法はいくつかあります。バッファ内の個々のバイトにアクセスしたり、全体の内容を抽出したりできます。

バッファの1バイトにアクセスするには、欲しいバイトのインデックスまたは位置を渡します。バッファは配列と同様にデータを順次格納します。また、配列のようにデータにインデックスを付け、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 ビットのシーケンスがあり、1 ビットあたり 2 つの可能な値がある場合、1 バイトあたりの可能な値の最大数は 2⁸ です。つまり、最大 256 個の値があります。ゼロから数え始めるので、最大の数は 255 です。

2 番目のバイトについても同じことをしましょう。REPL に次のように入力します:

  1. hiBuf[1];

REPL は 105 を返し、これは小文字の i を表しています。

最後に、3 番目の文字を取得しましょう:

  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'

文字列\u0000は、NULLのUnicode文字です。これは数値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オブジェクトには常にBufferであることを示すtypeプロパティがあります。これにより、プログラムが他のJSONオブジェクトとこれらのJSONオブジェクトを区別できます。

dataプロパティには、バイトの整数表現の配列が含まれています。バイトを個別に取得したときに受け取った値である72105、および33に対応することに気付いたかもしれません。

tenZeroestoJSON()メソッドを試してみましょう:

  1. tenZeroes.toJSON();

REPLでは、次のように表示されます:

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

typeは以前に注釈されたものと同じです。ただし、データは今回は10個のゼロの配列です。

バッファから読み取る主な方法をカバーしたので、バッファの内容を変更する方法を見てみましょう。

ステップ3 — バッファの変更

既存のバッファオブジェクトを変更する方法は多岐にわたります。読み取りと同様に、配列構文を使用してバッファバイトを個別に変更できます。また、既存のデータを置換する新しい内容をバッファに書き込むこともできます。

まず、バッファーの個々のバイトを変更する方法を見てみましょう。 文字列「Hi!」を含むhiBuf変数を思い出してください。 代わりにHeyを含むように、各バイトを変更しましょう。

REPLでは、まずhiBufの2番目の要素を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のバイトに対応する整数を第3要素に設定する必要があります:

  1. hiBuf[2] = 121;

再びtoString()メソッドを使用して確認しましょう:

  1. hiBuf.toString();

REPLには次のように表示されます:

Output
'Hey'

バッファーの範囲外のバイトを書き込もうとすると、無視され、バッファーの内容が変更されません。 たとえば、バッファーの存在しない4番目の要素をoに設定しようとしてみましょう:

  1. hiBuf[3] = 111;

toString()メソッドを使用してバッファーが変更されていないことを確認できます:

  1. hiBuf.toString();

出力はまだ次のようになります:

Output
'Hey'

バッファ全体の内容を変更したい場合は、write()メソッドを使用できます。 write()メソッドは、バッファの内容を置き換える文字列を受け入れます。

write()メソッドを使用して、hiBufの内容をHi!に変更しましょう。 Node.jsシェルで、以下のコマンドをプロンプトに入力します:

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

REPLでwrite()メソッドが3を返しました。これは、3バイトのデータが書き込まれたためです。各文字は1バイトのサイズを持ちます。なぜなら、このバッファはUTF-8エンコーディングを使用しており、各文字に1バイトを使用するからです。バッファがUTF-16エンコーディングを使用している場合、1文字あたり最低2バイトが必要なため、write()関数は6を返します。

次に、toString()を使用してバッファの内容を確認します:

  1. hiBuf.toString();

REPLは次のように返します:

Output
'Hi!'

これは、バイトごとに要素を個別に変更するよりも速い方法です。

バッファのサイズよりも多くのバイトを書き込もうとすると、バッファオブジェクトは収まるバイト数しか受け入れません。説明するために、3バイトを格納するバッファを作成しましょう:

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

次に、それにCatsを書き込もうとしてみましょう:

  1. petBuf.write('Cats');

write()呼び出しが評価されると、REPLは3を返し、バッファに書き込まれたバイト数を示します。次に、バッファが最初の3バイトを含むことを確認します:

  1. petBuf.toString();

REPLは次のように返します:

Output
'Cat'

write()関数はバイトを順番に追加するため、最初の3バイトのみがバッファに配置されます。

対照的に、4バイトを格納するBufferを作成しましょう:

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

同じ内容をそれに書き込んでください:

  1. petBuf2.write('Cats');

それから、元の内容よりもスペースを少なく占める新しいコンテンツを追加してください:

  1. petBuf2.write('Hi');

バッファは、0から始まる順に書き込まれるため、バッファの内容を出力すると:

  1. petBuf2.toString();

以下のように表示されます:

Output
'Hits'

最初の2文字が上書きされますが、バッファの残りの部分は変更されません。

時には、既存のバッファに欲しいデータが文字列ではなく、別のバッファオブジェクトに存在することがあります。この場合、バッファに格納される内容を変更するためにcopy()関数を使用できます。

2つの新しいバッファを作成しましょう:

  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バイトしか使用していないため、コピーが意図した通りに行われなかったことがすぐにわかります。 catchphraseBuf の内容を確認するために toString() メソッドを使用します:

  1. catchphraseBuf.toString();

REPL が返すのは:

Output
'Banana Nananana!'

copy() はデフォルトで wordsBuf の全内容を取り込み、それを catchphraseBuf に配置します。 私たちの目標にはもっと選択的になる必要があり、Nananana のみをコピーする必要があります。 続行する前に、catchphraseBuf の元の内容を書き直しましょう:

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

copy() 関数には、他のバッファにコピーするデータをカスタマイズするためのいくつかの追加のパラメータがあります。 この関数のすべてのパラメータのリストは次のとおりです:

  • target – これは copy() の唯一の必須パラメータです。 前の使用例からわかるように、コピー先のバッファです。
  • targetStart – これは、コピーを開始するターゲットバッファ内のバイトのインデックスです。 デフォルトでは 0 で、バッファの先頭からデータをコピーします。
  • sourceStart – これは、コピー元バッファ内のバイトのインデックスです。
  • sourceEnd – これは、コピー元バッファ内のバイトのインデックスで、コピーを停止する位置です。 デフォルトでは、バッファの長さです。

したがって、targetは前と同様にcatchphraseBufでなければなりません。targetStartcatchphraseBufの先頭にNanananaを表示したいので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のデータを変更することができました。

Node.js REPLを終了することができます。すべての作成された変数はもはや利用できなくなりますので注意してください:

  1. .exit

結論

このチュートリアルでは、バッファはメモリ内の固定長の割り当てで、バイナリデータを保存するものであることを学びました。最初に、メモリ内でサイズを定義し、既存のデータで初期化することでバッファを作成しました。そして、個々のバイトを調査してtoString()toJSON()メソッドを使用してバッファからデータを読み取りました。最後に、write()copy()メソッドを使用してバッファに格納されているデータを変更しました。

バッファを使用することで、Node.jsでバイナリデータがどのように操作されるかについて深く理解することができます。バッファとやり取りできるようになったので、文字エンコーディングがデータの格納方法にどのように影響するかを観察できます。たとえば、UTF-8やASCIIエンコーディングではない文字列データからバッファを作成し、サイズの違いを観察できます。また、UTF-8でエンコードされたバッファを取得し、toString()を使用して他のエンコーディングスキームに変換することもできます。

Node.jsでバッファについて学ぶには、BufferオブジェクトのNode.jsドキュメントを読むことができます。Node.jsの学習を続けたい場合は、Node.jsでコーディングする方法シリーズに戻るか、プログラミングプロジェクトやセットアップを私たちのNodeトピックページでブラウズすることができます。

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