Node.jsでのバッファーの使用
著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
バッファは、バイナリデータを格納するメモリ(通常はRAM)内のスペースです。 Node.js では、組み込みのBufferクラスを使用してこれらのメモリスペースにアクセスできます。 バッファには、JavaScriptのarrayと同様の整数のシーケンスが格納されます。 配列とは異なり、バッファが作成されると、バッファのサイズを変更することはできません。
すでにNode.jsコードを記述している場合は、暗黙的にバッファーを使用している可能性があります。 たとえば、 fs.readFile()を使用してファイルから読み取る場合、コールバックまたはPromiseに返されるデータはバッファーオブジェクトです。 さらに、HTTPリクエストがNode.jsで行われると、クライアントがストリームを一度に処理できない場合に、内部バッファーに一時的に保存されているデータストリームを返します。
バッファは、通常はより低いネットワークレベルでバイナリデータを操作する場合に役立ちます。 また、Node.jsできめ細かいデータ操作を行う機能も備えています。
このチュートリアルでは、 Node.js REPL を使用して、バッファーの作成、バッファーからの読み取り、バッファーへの書き込みとバッファーからのコピー、バッファーを使用したバイナリとエンコードされたデータ。 チュートリアルの終わりまでに、Bufferクラスを使用してバイナリデータを操作する方法を学習しました。
前提条件
- 開発マシンにNode.jsをインストールする必要があります。 このチュートリアルでは、バージョン10.19.0を使用します。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
- このチュートリアルでは、Node.js REPL(Read-Evaluate-Print-Loop)のバッファーを操作します。 Node.js REPLを効果的に使用する方法の復習が必要な場合は、 Node.jsREPLの使用方法に関するガイドをお読みください。
- この記事では、ユーザーが基本的なJavaScriptとそのデータ型に慣れていることを期待しています。 これらの基礎は、JavaScriptシリーズのコーディング方法で学ぶことができます。
ステップ1—バッファを作成する
この最初のステップでは、Node.jsでバッファーオブジェクトを作成する2つの主要な方法を示します。
使用する方法を決定するには、次の質問に答える必要があります。新しいバッファーを作成しますか、それとも既存のデータからバッファーを抽出しますか? まだ受信していないデータをメモリに保存する場合は、新しいバッファを作成する必要があります。 Node.jsでは、Buffer classのalloc()関数を使用してこれを行います。
Node.js REPL を開いて、自分の目で確かめてみましょう。 端末で、nodeコマンドを入力します。
node
>で始まるプロンプトが表示されます。
alloc()関数は、最初で唯一必要な引数としてバッファーのサイズを取ります。 サイズは、バッファオブジェクトが使用するメモリのバイト数を表す整数です。 たとえば、1024バイトに相当する1KB(キロバイト)の大きさのバッファを作成する場合は、コンソールに次のように入力します。
const firstBuf = Buffer.alloc(1024);
新しいバッファを作成するために、alloc()メソッドを持つグローバルに利用可能なBufferクラスを使用しました。 alloc()の引数として1024を指定することにより、1KBの大きさのバッファーを作成しました。
デフォルトでは、alloc()でバッファーを初期化すると、バッファーは後のデータのプレースホルダーとしてバイナリゼロで埋められます。 ただし、必要に応じてデフォルト値を変更できます。 0の代わりに1を使用して新しいバッファーを作成する場合は、alloc()関数の2番目のパラメーターであるfillを設定します。
ターミナルで、REPLプロンプトで1で満たされた新しいバッファを作成します。
const filledBuf = Buffer.alloc(1024, 1);
1KBの1を格納するメモリ内のスペースを参照する新しいバッファオブジェクトを作成しました。 整数を入力しましたが、バッファに格納されているデータはすべてバイナリデータです。
バイナリデータは、さまざまな形式で提供されます。 たとえば、1バイトのデータを表すバイナリシーケンス01110110について考えてみましょう。 このバイナリシーケンスが、 ASCIIエンコーディング標準を使用して英語で文字列を表す場合、文字vになります。 ただし、コンピュータが画像を処理している場合、そのバイナリシーケンスにはピクセルの色に関する情報が含まれている可能性があります。
バイトのエンコードが異なるため、コンピューターはそれらを異なる方法で処理することを認識しています。 バイトエンコーディングは、バイトの形式です。 Node.jsのバッファーは、文字列データで初期化されている場合、デフォルトでUTF-8エンコードスキームを使用します。 UTF-8のバイトは、数字、文字(英語およびその他の言語)、または記号を表します。 UTF-8は、情報交換のための米国標準コードであるASCIIのスーパーセットです。 ASCIIは、大文字と小文字の英字、数字の0〜9、および感嘆符( ! )またはアンパサンド記号( & )。
ASCII文字でのみ機能するプログラムを作成している場合、バッファで使用されるエンコーディングをalloc()関数の3番目の引数encodingで変更できます。
5バイトの長さで、ASCII文字のみを格納する新しいバッファを作成しましょう。
const asciiBuf = Buffer.alloc(5, 'a', 'ascii');
バッファは、ASCII表現を使用して、5バイトの文字aで初期化されます。
注:デフォルトでは、Node.jsは次の文字エンコードをサポートしています。
- ASCII 、
asciiとして表されます - UTF-8 、
utf-8またはutf8として表されます - UTF-16 、
utf-16leまたはutf16leとして表されます - UCS-2 、
ucs-2またはucs2として表されます - Base64 、
base64として表されます - 16進数、
hexとして表されます - ISO / IEC 8859-1 、
latin1またはbinaryとして表されます
これらの値はすべて、encodingパラメーターを受け入れるBufferクラス関数で使用できます。 したがって、これらの値はすべてalloc()メソッドに対して有効です。
これまで、alloc()関数を使用して新しいバッファーを作成してきました。 ただし、文字列や配列など、すでに存在するデータからバッファを作成したい場合があります。
既存のデータからバッファーを作成するには、from()メソッドを使用します。 この関数を使用して、次の場所からバッファを作成できます。
- 整数の配列:整数値は、
0と255の間にあります。 ArrayBuffer:これは固定長のバイトを格納するJavaScriptオブジェクトです。- 文字列。
- 別のバッファ。
Symbol.toPrimitiveプロパティを持つ他のJavaScriptオブジェクト。 このプロパティは、オブジェクトをプリミティブデータ型に変換する方法をJavaScriptに指示します:boolean、null、undefined、number、string、またはsymbol。 シンボルの詳細については、MozillaのJavaScriptドキュメントを参照してください。
文字列からバッファを作成する方法を見てみましょう。 Node.jsプロンプトで、次のように入力します。
const stringBuf = Buffer.from('My name is Paul');
これで、文字列My name is Paulから作成されたバッファオブジェクトができました。 以前に作成した別のバッファーから新しいバッファーを作成しましょう。
const asciiCopy = Buffer.from(asciiBuf);
これで、asciiBufと同じデータを含む新しいバッファーasciiCopyが作成されました。
バッファの作成を経験したので、それらのデータを読み取る例に飛び込むことができます。
ステップ2—バッファからの読み取り
バッファ内のデータにアクセスする方法はたくさんあります。 バッファ内の個々のバイトにアクセスすることも、コンテンツ全体を抽出することもできます。
バッファの1バイトにアクセスするには、必要なバイトのインデックスまたは場所を渡します。 バッファは、配列のようにデータを順番に格納します。 また、0から始まる配列のようにデータにインデックスを付けます。 バッファオブジェクトで配列表記を使用して、個々のバイトを取得できます。
REPLの文字列からバッファを作成して、これがどのように見えるかを見てみましょう。
const hiBuf = Buffer.from('Hi!');
次に、バッファの最初のバイトを読み取ります。
hiBuf[0];
ENTERを押すと、REPLは次のように表示します。
Output72
整数72は、文字HのUTF-8表現に対応します。
注:バイトの値は、0と255の間の数値にすることができます。 バイトは8ビットのシーケンスです。 ビットはバイナリであるため、0または1の2つの値のいずれかのみを持つことができます。 8ビットのシーケンスとビットごとに2つの可能な値がある場合、1バイトに対して最大2⁸の可能な値があります。 これは最大256の値になります。 ゼロから数え始めるので、それは私たちの最大数が255であることを意味します。
2番目のバイトについても同じことをしましょう。 REPLに次のように入力します。
hiBuf[1];
REPLは、小文字のiを表す105を返します。
最後に、3番目の文字を取得しましょう:
hiBuf[2];
!に対応する33がREPLに表示されます。
無効なインデックスからバイトを取得してみましょう。
hiBuf[3];
REPLは以下を返します:
Outputundefined
これは、インデックスが正しくない配列内の要素にアクセスしようとした場合と同じです。
バッファの個々のバイトを読み取る方法を確認したので、バッファに格納されているすべてのデータを一度に取得するためのオプションを見てみましょう。 バッファオブジェクトには、toString()メソッドとtoJSON()メソッドが付属しており、バッファの内容全体を2つの異なる形式で返します。
その名前が示すように、toString()メソッドは、バッファーのバイトを文字列に変換し、それをユーザーに返します。 hiBufでこのメソッドを使用すると、文字列Hi!が取得されます。 試してみよう!
プロンプトで、次のように入力します。
hiBuf.toString();
REPLは以下を返します:
Output'Hi!'
そのバッファは文字列から作成されました。 文字列データから作成されていないバッファでtoString()を使用するとどうなるか見てみましょう。
10バイトの大きさの新しい空のバッファーを作成しましょう。
const tenZeroes = Buffer.alloc(10);
それでは、toString()メソッドを使用してみましょう。
tenZeroes.toString();
次の結果が表示されます。
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
文字列\u0000は、NULLのUnicode文字です。 番号0に対応します。 バッファのデータが文字列としてエンコードされていない場合、toString()メソッドはバイトのUTF-8エンコードを返します。
toString()には、オプションのパラメーターencodingがあります。 このパラメーターを使用して、返されるバッファーデータのエンコーディングを変更できます。
たとえば、hiBufの16進エンコーディングが必要な場合は、プロンプトで次のように入力します。
hiBuf.toString('hex');
そのステートメントは次のように評価されます。
Output'486921'
486921は、文字列Hi!を表すバイトの16進表現です。 Node.jsでは、ユーザーがデータのエンコードをある形式から別の形式に変換する場合、通常、文字列をバッファーに入れ、目的のエンコードでtoString()を呼び出します。
toJSON()メソッドの動作は異なります。 バッファが文字列から作成されたかどうかに関係なく、常にバイトの整数表現としてデータを返します。
hiBufおよびtenZeroesバッファーを再利用して、toJSON()の使用を練習してみましょう。 プロンプトで、次のように入力します。
hiBuf.toJSON();
REPLは以下を返します:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
JSONオブジェクトにはtypeプロパティがあり、常にBufferになります。 そのため、プログラムはこれらのJSONオブジェクトを他のJSONオブジェクトと区別できます。
dataプロパティには、バイトの整数表現の配列が含まれています。 72、105、および33は、バイトを個別にプルしたときに受け取った値に対応していることに気付いたかもしれません。
tenZeroesでtoJSON()メソッドを試してみましょう。
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に設定してみましょう。
hiBuf[1] = 'e';
次に、このバッファを文字列として見て、正しいデータが格納されていることを確認しましょう。 toString()メソッドを呼び出してフォローアップします。
hiBuf.toString();
次のように評価されます。
Output'H\u0000!'
バッファは整数値しか受け入れられないため、この奇妙な出力を受け取りました。 文字eに割り当てることはできません。 むしろ、それに相当するバイナリがeを表す番号を割り当てる必要があります。
hiBuf[1] = 101;
ここで、toString()メソッドを呼び出すと、次のようになります。
hiBuf.toString();
この出力はREPLで取得されます。
Output'He!'
バッファの最後の文字を変更するには、3番目の要素をyのバイトに対応する整数に設定する必要があります。
hiBuf[2] = 121;
toString()メソッドをもう一度使用して確認しましょう。
hiBuf.toString();
REPLは次のように表示されます。
Output'Hey'
バッファの範囲外のバイトを書き込もうとすると、それは無視され、バッファの内容は変更されません。 たとえば、バッファの存在しない4番目の要素をoに設定してみましょう。
hiBuf[3] = 111;
toString()メソッドを使用すると、バッファーが変更されていないことを確認できます。
hiBuf.toString();
出力はまだです:
Output'Hey'
バッファ全体の内容を変更したい場合は、write()メソッドを使用できます。 write()メソッドは、バッファーの内容を置き換える文字列を受け入れます。
write()メソッドを使用して、hiBufの内容をHi!に戻しましょう。 Node.jsシェルで、プロンプトで次のコマンドを入力します。
hiBuf.write('Hi!');
write()メソッドは、REPLで3を返しました。 これは、3バイトのデータを書き込んだためです。 このバッファは各文字に1バイトを使用するUTF-8エンコーディングを使用するため、各文字のサイズは1バイトです。 バッファがUTF-16エンコーディングを使用している場合(1文字あたり最低2バイト)、write()関数は6を返します。
次に、toString()を使用して、バッファーの内容を確認します。
hiBuf.toString();
REPLは以下を生成します:
Output'Hi!'
これは、各要素をバイトごとに変更するよりも高速です。
バッファのサイズよりも多くのバイトを書き込もうとすると、バッファオブジェクトは適切なバイトのみを受け入れます。 説明のために、3バイトを格納するバッファを作成しましょう。
const petBuf = Buffer.alloc(3);
それでは、Catsを書き込んでみましょう。
petBuf.write('Cats');
write()呼び出しが評価されると、REPLは3を返し、3バイトのみがバッファーに書き込まれたことを示します。 次に、バッファに最初の3バイトが含まれていることを確認します。
petBuf.toString();
REPLは以下を返します:
Output'Cat'
write()関数はバイトを順番に追加するため、最初の3バイトのみがバッファーに配置されました。
対照的に、4バイトを格納するBufferを作成しましょう。
const petBuf2 = Buffer.alloc(4);
それに同じ内容を書いてください:
petBuf2.write('Cats');
次に、元のコンテンツよりも占有するスペースが少ない新しいコンテンツを追加します。
petBuf2.write('Hi');
バッファは0から順番に書き込むため、バッファの内容を出力すると、次のようになります。
petBuf2.toString();
私たちは次のように迎えられます:
Output'Hits'
最初の2文字は上書きされますが、残りのバッファーは変更されません。
既存のバッファに必要なデータが文字列ではなく、別のバッファオブジェクトに存在する場合があります。 このような場合、copy()関数を使用して、バッファーに格納されている内容を変更できます。
2つの新しいバッファを作成しましょう。
const wordsBuf = Buffer.from('Banana Nananana');
const catchphraseBuf = Buffer.from('Not sure Turtle!');
wordsBufおよびcatchphraseBufバッファーには、両方とも文字列データが含まれています。 Not sure Turtle!ではなくNananana Turtle!を格納するように、catchphraseBufを変更します。 copy()を使用して、NanananaをwordsBufからcatchphraseBufに取得します。
あるバッファから別のバッファにデータをコピーするには、情報のソースであるバッファでcopy()メソッドを使用します。 したがって、wordsBufにはコピーする文字列データがあるため、次のようにコピーする必要があります。
wordsBuf.copy(catchphraseBuf);
この場合のtargetパラメーターは、catchphraseBufバッファーです。
これをREPLに入力すると、15バイトが書き込まれたことを示す15が返されます。 文字列Nanananaは8バイトのデータしか使用しないため、コピーが意図したとおりに行われなかったことがすぐにわかります。 toString()メソッドを使用して、catchphraseBufの内容を確認します。
catchphraseBuf.toString();
REPLは以下を返します:
Output'Banana Nananana!'
デフォルトでは、copy()はwordsBufの内容全体を取得し、catchphraseBufに配置しました。 目標をより厳選し、Nanananaのみをコピーする必要があります。 続行する前に、catchphraseBufの元の内容を書き直してみましょう。
catchphraseBuf.write('Not sure Turtle!');
copy()関数には、他のバッファーにコピーされるデータをカスタマイズできるパラメーターがいくつかあります。 この関数のすべてのパラメーターのリストは次のとおりです。
target-これはcopy()の唯一の必須パラメーターです。 以前の使用法から見てきたように、これはコピー先のバッファーです。targetStart-これは、コピーを開始する必要があるターゲットバッファ内のバイトのインデックスです。 デフォルトでは0です。これは、バッファーの先頭からデータをコピーすることを意味します。sourceStart-これは、コピー元のソースバッファ内のバイトのインデックスです。sourceEnd-これは、コピーを停止する必要があるソースバッファ内のバイトのインデックスです。 デフォルトでは、これはバッファの長さです。
したがって、NanananaをwordsBufからcatchphraseBufにコピーするには、targetを以前のようにcatchphraseBufにする必要があります。 NanananaはcatchphraseBufの先頭に表示するため、targetStartは0になります。 sourceStartは7である必要があります。これは、NanananaがwordsBufで始まるインデックスです。 sourceEndは、引き続きバッファーの長さになります。
REPLプロンプトで、wordsBufの内容を次のようにコピーします。
wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length);
REPLは、8バイトが書き込まれたことを確認します。 wordsBuf.lengthがsourceEndパラメーターの値としてどのように使用されているかに注意してください。 配列と同様に、lengthプロパティはバッファのサイズを示します。
次に、catchphraseBufの内容を見てみましょう。
catchphraseBuf.toString();
REPLは以下を返します:
Output'Nananana Turtle!'
成功! wordsBufの内容をコピーすることで、catchphraseBufのデータを変更することができました。
必要に応じて、Node.jsREPLを終了できます。 次の場合、作成されたすべての変数が使用できなくなることに注意してください。
.exit
結論
このチュートリアルでは、バッファがバイナリデータを格納するメモリ内の固定長の割り当てであることを学びました。 最初に、メモリ内のサイズを定義し、既存のデータでバッファを初期化することにより、バッファを作成しました。 次に、個々のバイトを調べ、toString()およびtoJSON()メソッドを使用して、バッファーからデータを読み取ります。 最後に、個々のバイトを変更し、write()およびcopy()メソッドを使用して、バッファーに格納されているデータを変更しました。
バッファーは、Node.jsによってバイナリデータがどのように操作されるかについての優れた洞察を提供します。 バッファを操作できるようになったので、文字エンコードがデータの格納方法にどのように影響するかを観察できます。 たとえば、UTF-8またはASCIIエンコーディングではない文字列データからバッファを作成し、それらのサイズの違いを観察できます。 UTF-8でバッファーを取得し、toString()を使用して他のエンコード方式に変換することもできます。
Node.jsのバッファーについては、BufferオブジェクトのNode.jsドキュメントをご覧ください。 Node.jsの学習を継続したい場合は、 Node.jsシリーズのコーディング方法に戻るか、Nodeトピックページでプログラミングプロジェクトとセットアップを参照してください。