はじめてのNode.js:Node.js内でバイナリデータを扱うための「Buffer」クラス 2ページ

Buffer型とString型の相互変換——buf.toStringメソッド、buf.writeメソッド、StringDecoderクラス

 Bufferクラスに格納したデータを文字列として扱いたい場合、toStringメソッドを利用して格納されたデータをString型に変換することができる。

buf.toString([encoding], [start], [end])

 encoding引数には格納されたデータの文字エンコーディングを指定する。encoding引数が省略された場合は「utf8」が指定されたものとして変換処理が実行される。start引数およびend引数には変換を開始する位置と終了する位置をバイト単位で指定する。変換結果の文字列はメソッドの戻り値として返される。

 いっぽう文字列をバイナリ列として扱いたい場合、Bufferクラスのコンストラクタに引数として文字列を与えて新たなBuffer型オブジェクトを作成すればよい。また、既存のBufferオブジェクトに文字列を書き込みたい場合はwriteメソッドを使用する。

buf.write(string, [offset], [length], [encoding])

 string引数には変換したい文字列を、offset引数およびlength引数には返還後のバイナリデータを格納する位置を指定する。変換後の文字列は、オブジェクトに格納されているバイト列の最初からoffsetバイト目の位置からoffset+lengthバイト目の位置に格納される。それぞれのデフォルト値は0およびbuffer.length – offsetとなっている。

 そのほか、StringDecoderクラスを用いてBuffer型オブジェクトを文字列に変換することもできる。この場合、まずnewキーワードを使ってStringDecoderクラスのインスタンスを作成する。

string_decoder.StringDecoder([encoding])

 このとき、encoding引数には使用するエンコーディングを文字列で指定する。デフォルトはutf8だ。Buffer型オブジェクトから文字列への変換には、StringDecoderクラスのWriteメソッドを使用する。

decoder.write(buffer)

 writeメソッドではbuffer引数に変換したいBuffer型オブジェクトを与えると、戻り値として指定されたエンコーディングを使って変換された文字列が戻り値として返される。

 StringDecoderが有用なのは、複数のBuffer型オブジェクトに渡って格納されたバイナリデータを文字列に変換したい場合に有効だ。たとえばUTF-8では、文字1文字を複数バイトで表現する。

> new Buffer('あ')
<Buffer e3 81 82>

 「あいう」という文字列をUTF-8で表現すると、16進表記で「e3 81 82 e3 81 84 e3 81 86」という9バイトのバイナリとなる。

> buf = new Buffer('あいう')
<Buffer e3 81 82 e3 81 84 e3 81 86>

 このバイナリが2つのBufferに分割されて格納されているケースを考えてみよう。たとえば、次のように6バイト目で分割した場合、それぞれをtoStringメソッドで文字列に変換すると文字化けが発生する。「あ」「い」「う」はそれぞれ3バイトで表現される文字だが、この例では2文字目の途中で分割を行っているからだ。

> var str2 = new Buffer([0xe3, 0x81, 0x82, 0xe3, 0x81]);
undefined
> var str3 = new Buffer([0x84, 0xe3, 0x81, 0x86]);
undefined
> str2.toString()
'あ��'
> str3.toString()
'�う'

 次のように2つのバッファを結合してからtoStringメソッドを実行すれば正しく文字列に変換できるのだが、この操作はオブジェクトのサイズが大きくなった場合効率が悪い。

> Buffer.concat([str2, str3]).toString()
'あいう'

 StringDecoderクラスを用いた変換では、このようにBufferが文字の途中で分割されている場合でも正しく文字列へと変換できる。

> string_decoder
{ StringDecoder: [Function] }
> var decoder = new string_decoder.StringDecoder();
undefined
> decoder.write(str2);
'あ'
> decoder.write(str3);
'いう'

Bufferと数値の相互変換——buf.read*メソッドおよびbuf.write*メソッド

 JavaScriptでは数値はすべてNumber型として扱われる。整数(int)や浮動小数点(floatやdouble)などを明確に区別する言語もあるが、JavaScriptではこれらは区別されない。これは処理がJavaScript内だけで解決する場合にはよいが、たとえばファイルの読み書きを行う場合など、数値データの型を明確に意識しなければならないときもある。このような処理に向け、Bufferクラスには指定した位置のバイナリデータをJavaScriptのNumber型で読み出したり、Number型のデータを指定した型でBufferに格納するメソッドが用意されている(表2)。

表2 Bufferクラスの数値読み書き用メソッド
メソッド名読み書きを行う数値データ
buf.readUInt8(offset, [noAssert])8ビット符合無し整数
buf.writeUInt8(value, offset, [noAssert])
buf.readUInt16LE(offset, [noAssert])16ビット符合無し整数(リトルエンディアン)
buf.writeUInt16LE(value, offset, [noAssert])
buf.readUInt16BE(offset, [noAssert])16ビット符合無し整数(ビッグエンディアン)
buf.writeUInt16BE(value, offset, [noAssert])
buf.readUInt32LE(offset, [noAssert])32ビット符合無し整数(リトルエンディアン)
buf.writeUInt32LE(value, offset, [noAssert])
buf.readUInt32BE(offset, [noAssert])16ビット符合無し整数(ビッグエンディアン)
buf.writeUInt32BE(value, offset, [noAssert])
buf.readInt8(offset, [noAssert])8ビット符合あり整数
buf.writeInt8(value, offset, [noAssert])
buf.readInt16LE(offset, [noAssert])16ビット符合あり整数(リトルエンディアン)
buf.writeInt16LE(value, offset, [noAssert])
buf.readInt16BE(offset, [noAssert])16ビット符合無し整数(ビッグエンディアン)
buf.writeInt16BE(value, offset, [noAssert])
buf.readInt32LE(offset, [noAssert])32ビット符合あり整数(リトルエンディアン)
buf.writeInt32LE(value, offset, [noAssert])
buf.readInt32BE(offset, [noAssert])32ビット符合あり整数(ビッグエンディアン)
buf.writeInt32BE(value, offset, [noAssert])
buf.readFloatLE(offset, [noAssert])単精度浮動小数点数(リトルエンディアン)
buf.writeFloatLE(value, offset, [noAssert])
buf.readFloatBE(offset, [noAssert])単精度浮動小数点数(ビッグエンディアン)
buf.writeFloatBE(value, offset, [noAssert])
buf.readDoubleLE(offset, [noAssert])倍精度浮動小数点数(リトルエンディアン)
buf.writeDoubleLE(value, offset, [noAssert])
buf.readDoubleBE(offset, [noAssert])倍精度浮動小数点数(ビッグエンディアン)
buf.writeDoubleBE(value, offset, [noAssert])

 これらメソッドの引数や使い方は変換元・変換先の型に関わらずどれも同じなので、ここではreadUInt8およびwriteUInt8メソッドについてのみ紹介をしておこう。まずread系のメソッドであるが、これらはバッファ内のデータを指定した形式でNumber型のデータに変換して取り出すものだ。

buf.readUInt8(offset, [noAssert])

 offset引数では取得するデータの位置を整数で指定する。また、noAssert引数にはoffsetの値を検証するかどうかをbool型で指定する。デフォルトではfalseに設定されており、この場合offsetにバッファの終端を越える値が指定された場合AssertionError例外が発生する。いっぽうnoAssert引数でtrueを指定した場合、offsetにバッファの終端を越える値が指定されても例外は発生しない。

> buf = new Buffer([0x00, 0x10, 0x20, 0x30])
<Buffer 00 10 20 30>
> buf.readUInt8(0) // バッファの0バイト目から符合無し整数で値を取り出す
0
> buf.readUInt8(1) // バッファの1バイト目から符合無し整数で値を取り出す
16
> buf.readUInt8(2) // バッファの2バイト目から符合無し整数で値を取り出す
32
> buf.readUInt8(3) // バッファの3バイト目から符合無し整数で値を取り出す
48
> buf.readUInt8(4) // バッファのサイズは4バイトなのでエラーとなる
AssertionError: Trying to read beyond buffer length
    at Buffer.readUInt8 (buffer.js:557:12)
    at repl:1:6
    at REPLServer.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:250:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:176:10)
    at Interface._line (readline.js:477:8)
    at Interface._ttyWrite (readline.js:690:14)
    at ReadStream.<anonymous> (readline.js:103:12)
    at ReadStream.emit (events.js:88:20)
> buf.readUInt8(4, true) // noAssert引数にtrueを指定すると、バッファの範囲を越えるoffsetを指定した場合でも例外が発生しない
undefined

 write系のメソッドは、Number型のオブジェクトを指定した型に変換してバッファに格納するものだ。こちらはvalueおよびoffset、noAssertという3つの引数を取る。

buf.writeUInt8(value, offset, [noAssert])

 value引数には書き込みたいデータを、offset引数にはデータを書き込む位置を整数で指定する。noAssert引数はread系メソッドと同様、offsetの値を検証するかどうかをbool型で指定するものだ。デフォルトではfalseに設定されており、この場合offsetにバッファの終端を越える値が指定された場合AssertionError例外が発生する。いっぽうnoAssert引数でtrueを指定した場合、offsetにバッファの終端を越える値が指定された場合でも例外は発生しない。

> buf = new Buffer([0x00, 0x10, 0x20, 0x30])
<Buffer 00 10 20 30>
> buf.writeUInt8(128, 0) // バッファの0バイト目に符合無し整数の「128」を書き込む
undefined
> buf
<Buffer 80 10 20 30>
> buf.writeUInt8(255, 4) // バッファのサイズは4バイトなのでエラーとなる
AssertionError: trying to write beyond buffer length
    at Buffer.writeUInt8 (buffer.js:837:12)
    at repl:1:6
    at REPLServer.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:250:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:176:10)
    at Interface._line (readline.js:477:8)
    at Interface._ttyWrite (readline.js:690:14)
    at ReadStream.<anonymous> (readline.js:103:12)
    at ReadStream.emit (events.js:88:20)
> buf.writeUInt8(255, 4, true)  // noAssert引数にtrueを指定すると、バッファの範囲を越えるoffsetを指定した場合無視される
undefined
> buf
<Buffer 80 10 20 30>

 また、value引数に指定した型では扱えない範囲の値が指定された場合もAssertionError例外が発生する。

> buf.writeUInt8(256, 3)
AssertionError: value is larger than maximum value for type
    at verifuint (buffer.js:822:10)
    at Buffer.writeUInt8 (buffer.js:840:5)
    at repl:1:6
    at REPLServer.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:250:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:176:10)
    at Interface._line (readline.js:477:8)
    at Interface._ttyWrite (readline.js:690:14)
    at ReadStream.<anonymous> (readline.js:103:12)