著者は、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では、これを行うためにBuffer
のalloc()
関数を使用します。
実際に確認するために、Node.js REPLを開いてみましょう。ターミナルで、node
コマンドを入力してください。
プロンプトが>
で始まるのが見えるでしょう。
alloc()
関数は、バッファのサイズを最初の必須引数として取ります。サイズは、バッファオブジェクトが使用するメモリのバイト数を表す整数です。例えば、1キロバイト(1024バイトに相当する)の大きさのバッファを作成したい場合は、コンソールにこれを入力します:
新しいバッファを作成するには、グローバルで利用可能なBuffer
クラスを使用しました。このクラスにはalloc()
メソッドがあります。 alloc()
に引数として1024
を指定することで、1KBのバッファを作成しました。
alloc()
でバッファを初期化すると、デフォルトで後でデータを置くためのバイナリゼロでバッファが埋められます。ただし、デフォルト値を変更することもできます。たとえば、0
ではなく1
で新しいバッファを作成したい場合は、alloc()
関数の第2引数であるfill
を設定します。
端末で、1
で満たされた新しいバッファをREPLプロンプトで作成してください:
これで、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バイトのバッファを作成しましょう:
バッファは、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オブジェクトです。- A string.
- 別のバッファ。
- 他のJavaScriptオブジェクトには、
Symbol.toPrimitive
プロパティがあります。このプロパティは、オブジェクトをプリミティブデータ型に変換する方法をJavaScriptに伝えます:boolean
、null
、undefined
、number
、string
、またはsymbol
。Symbolsについて詳しくは、MozillaのJavaScript documentationで読むことができます。
文字列からバッファを作成する方法を見てみましょう。Node.jsのプロンプトに、次のように入力します:
これで、文字列My name is Paul
から作成されたバッファオブジェクトができました。以前に作成した別のバッファから新しいバッファを作成しましょう:
これで、asciiBuf
と同じデータを含む新しいバッファasciiCopy
が作成されました。
バッファの作成を体験したので、そのデータを読み取る例に入ってみましょう。
ステップ2 — バッファからの読み取り
バッファ内のデータにアクセスする方法はいくつかあります。バッファ内の個々のバイトにアクセスしたり、全体の内容を抽出したりできます。
バッファの1バイトにアクセスするには、欲しいバイトのインデックスまたは位置を渡します。バッファは配列と同様にデータを順次格納します。また、配列のようにデータにインデックスを付け、0
から始まります。バッファオブジェクトで配列の記法を使用して個々のバイトを取得できます。
さて、REPL で文字列からバッファを作成して、どのように見えるかを見てみましょう:
今度は、バッファの最初のバイトを読み取りましょう:
ENTER キーを押すと、REPL に次のように表示されます:
Output72
整数 72
は、文字 H
の UTF-8 表現に対応しています。
注意: バイトの値は、0
から 255
までの数字であることがあります。バイトは 8 ビットのシーケンスです。ビットはバイナリであり、したがって 0
または 1
の値しか持つことができません。8 ビットのシーケンスがあり、1 ビットあたり 2 つの可能な値がある場合、1 バイトあたりの可能な値の最大数は 2⁸ です。つまり、最大 256 個の値があります。ゼロから数え始めるので、最大の数は 255 です。
2 番目のバイトについても同じことをしましょう。REPL に次のように入力します:
REPL は 105
を返し、これは小文字の i
を表しています。
最後に、3 番目の文字を取得しましょう:
REPL には 33
が表示され、これは !
に対応します。
無効なインデックスからバイトを取得しようとしてみましょう:
REPL は次のように返します:
Outputundefined
これは、配列内の要素に誤ったインデックスでアクセスしようとした場合と同じです。
これで、バッファの個々のバイトを読み取る方法を見てきました。次に、バッファに格納されているデータ全体を取得するオプションを見てみましょう。バッファオブジェクトには、バッファの内容全体を異なる形式で返す toString()
メソッドと toJSON()
メソッドが付属しています。
その名前が示すように、toString()
メソッドはバッファのバイトを文字列に変換してユーザーに返します。このメソッドをhiBuf
に使用すると、文字列Hi!
が得られます。試してみましょう!
プロンプトに次のように入力します:
REPLは次のように返します:
Output'Hi!'
そのバッファは文字列から作成されました。文字列データから作成されていないバッファでtoString()
を使用した場合の動作を見てみましょう。
サイズが10
バイトの新しい空のバッファを作成しましょう:
次に、toString()
メソッドを使用します:
次の結果が表示されます:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
文字列\u0000
は、NULL
のUnicode文字です。これは数値0
に対応しています。バッファのデータが文字列としてエンコードされていない場合、toString()
メソッドはバイトのUTF-8エンコーディングを返します。
toString()
にはオプションのパラメーターencoding
があります。このパラメーターを使用して、返されるバッファデータのエンコーディングを変更できます。
例えば、hiBuf
の16進エンコーディングを取得したい場合、次のようにプロンプトに入力します:
このステートメントは次のように評価されます:
Output'486921'
486921
は文字列Hi!
を表すバイトの16進表現です。Node.jsでは、データのエンコーディングを一つの形式から別の形式に変換する場合、通常、ユーザーは文字列をバッファに入れてtoString()
を呼び出します。それには、希望のエンコーディングを指定します。
toJSON()
メソッドの動作は異なります。文字列から作成されたかどうかに関係なく、常にバイトの整数表現としてデータを返します。
hiBuf
とtenZeroes
バッファを再利用して、toJSON()
を使用する練習をしましょう。プロンプトで次のように入力します:
REPLは次のように返します:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
JSONオブジェクトには常にBuffer
であることを示すtype
プロパティがあります。これにより、プログラムが他のJSONオブジェクトとこれらのJSONオブジェクトを区別できます。
data
プロパティには、バイトの整数表現の配列が含まれています。バイトを個別に取得したときに受け取った値である72
、105
、および33
に対応することに気付いたかもしれません。
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
に設定してみましょう:
次に、このバッファーを文字列として表示して、正しいデータが格納されていることを確認しましょう。 toString()
メソッドを呼び出して確認します:
評価結果は次のようになります:
Output'H\u0000!'
バッファーは整数値のみを受け入れるため、奇妙な出力が表示されます。 e
ではなく、e
を表すバイナリ値に相当する数値を割り当てる必要があります:
今度はtoString()
メソッドを呼び出すと:
REPLではこの出力が表示されます:
Output'He!'
バッファーの最後の文字を変更するには、y
のバイトに対応する整数を第3要素に設定する必要があります:
再びtoString()
メソッドを使用して確認しましょう:
REPLには次のように表示されます:
Output'Hey'
バッファーの範囲外のバイトを書き込もうとすると、無視され、バッファーの内容が変更されません。 たとえば、バッファーの存在しない4番目の要素をo
に設定しようとしてみましょう:
toString()
メソッドを使用してバッファーが変更されていないことを確認できます:
出力はまだ次のようになります:
Output'Hey'
バッファ全体の内容を変更したい場合は、write()
メソッドを使用できます。 write()
メソッドは、バッファの内容を置き換える文字列を受け入れます。
write()
メソッドを使用して、hiBuf
の内容をHi!
に変更しましょう。 Node.jsシェルで、以下のコマンドをプロンプトに入力します:
REPLでwrite()
メソッドが3
を返しました。これは、3バイトのデータが書き込まれたためです。各文字は1バイトのサイズを持ちます。なぜなら、このバッファはUTF-8エンコーディングを使用しており、各文字に1バイトを使用するからです。バッファがUTF-16エンコーディングを使用している場合、1文字あたり最低2バイトが必要なため、write()
関数は6
を返します。
次に、toString()
を使用してバッファの内容を確認します:
REPLは次のように返します:
Output'Hi!'
これは、バイトごとに要素を個別に変更するよりも速い方法です。
バッファのサイズよりも多くのバイトを書き込もうとすると、バッファオブジェクトは収まるバイト数しか受け入れません。説明するために、3バイトを格納するバッファを作成しましょう:
次に、それにCats
を書き込もうとしてみましょう:
write()
呼び出しが評価されると、REPLは3
を返し、バッファに書き込まれたバイト数を示します。次に、バッファが最初の3バイトを含むことを確認します:
REPLは次のように返します:
Output'Cat'
write()
関数はバイトを順番に追加するため、最初の3バイトのみがバッファに配置されます。
対照的に、4バイトを格納するBuffer
を作成しましょう:
同じ内容をそれに書き込んでください:
それから、元の内容よりもスペースを少なく占める新しいコンテンツを追加してください:
バッファは、0
から始まる順に書き込まれるため、バッファの内容を出力すると:
以下のように表示されます:
Output'Hits'
最初の2文字が上書きされますが、バッファの残りの部分は変更されません。
時には、既存のバッファに欲しいデータが文字列ではなく、別のバッファオブジェクトに存在することがあります。この場合、バッファに格納される内容を変更するためにcopy()
関数を使用できます。
2つの新しいバッファを作成しましょう:
wordsBuf
とcatchphraseBuf
バッファは、両方とも文字列データを含んでいます。 catchphraseBuf
がNot sure Turtle!
ではなくNananana Turtle!
を格納するように変更したいです。 wordsBuf
からNananana
をcatchphraseBuf
にコピーするためにcopy()
を使用します。
一方のバッファから他方のバッファにデータをコピーするには、情報源のバッファにcopy()
メソッドを使用します。したがって、欲しい文字列データを持つwordsBuf
からコピーする必要があります:
この場合、target
パラメータはcatchphraseBuf
バッファです。
REPL にそれを入力すると、15
が返され、15 バイトが書き込まれたことを示します。 文字列 Nananana
はデータの8バイトしか使用していないため、コピーが意図した通りに行われなかったことがすぐにわかります。 catchphraseBuf
の内容を確認するために toString()
メソッドを使用します:
REPL が返すのは:
Output'Banana Nananana!'
copy()
はデフォルトで wordsBuf
の全内容を取り込み、それを catchphraseBuf
に配置します。 私たちの目標にはもっと選択的になる必要があり、Nananana
のみをコピーする必要があります。 続行する前に、catchphraseBuf
の元の内容を書き直しましょう:
copy()
関数には、他のバッファにコピーするデータをカスタマイズするためのいくつかの追加のパラメータがあります。 この関数のすべてのパラメータのリストは次のとおりです:
target
– これはcopy()
の唯一の必須パラメータです。 前の使用例からわかるように、コピー先のバッファです。targetStart
– これは、コピーを開始するターゲットバッファ内のバイトのインデックスです。 デフォルトでは0
で、バッファの先頭からデータをコピーします。sourceStart
– これは、コピー元バッファ内のバイトのインデックスです。sourceEnd
– これは、コピー元バッファ内のバイトのインデックスで、コピーを停止する位置です。 デフォルトでは、バッファの長さです。
したがって、target
は前と同様にcatchphraseBuf
でなければなりません。targetStart
はcatchphraseBuf
の先頭にNananana
を表示したいので0
であるべきです。sourceStart
はNananana
がwordsBuf
で始まるインデックスである7
でなければなりません。sourceEnd
はバッファの長さのままである必要があります。
REPLプロンプトで、wordsBuf
の内容を次のようにコピーします:
REPLは8
バイトが書き込まれたことを確認します。wordsBuf.length
がsourceEnd
パラメータの値として使用されていることに注意してください。配列と同様に、length
プロパティはバッファのサイズを返します。
さて、catchphraseBuf
の内容を見てみましょう:
REPLは次のように返します:
Output'Nananana Turtle!'
成功です! wordsBuf
の内容をコピーしてcatchphraseBuf
のデータを変更することができました。
Node.js REPLを終了することができます。すべての作成された変数はもはや利用できなくなりますので注意してください:
結論
このチュートリアルでは、バッファはメモリ内の固定長の割り当てで、バイナリデータを保存するものであることを学びました。最初に、メモリ内でサイズを定義し、既存のデータで初期化することでバッファを作成しました。そして、個々のバイトを調査して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