コミケ告知

サークル活動の詳細は circle タグの記事へ。
2012年7月12日木曜日

ByteBufferの挙動を確かめるには

# はてなダイアリーから移動した記事です。あまり真面目に整形していません。

java.nio.ByteBufferってクラスがあります。Byteの配列を直接扱うのはクールではない、という純情な感情から生まれたクラスに違いありません。たぶん。

putInt(x)や getFloat() のようなひとつ上のレイヤーのメソッドで値を出し入れ出来たり、asLongBuffer()などで特定型が並んだものとして扱えたりと、何かと便利です。

ただ、隠蔽されているメンバ変数、特にpositionの動きには、慣れるまでは翻弄されるかもしれません。メソッドによっては自動的にインクリメントしてくれたり、ByteBufferを受け取るメソッド内で読み取っていたりするので、何がどうなっているかわからずに使うと、はまる可能性があります。参照透明性とは真逆の存在ですね。仮にasReadOnlyBufferで変換しても、中でpositionやlimitは勝手に動くので。

対話環境を起動

こういうときは対話環境、すなわちコマンド打ったらその場で実行してくれるような環境で遊んでみるのが分かりやすいです。おもむろにscalaのREPL(対話環境)を起動!
Javaユーザーも、とりあえずScalaをインストールしましょう。一行ずつコマンド打つ程度なら、大して違うものでもないので大丈夫。

~ % scala
scala> import java.nio.ByteBuffer
import java.nio.ByteBuffer

scala> ByteBuffer.allocate(128)
res0: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=0 lim=128 cap=128]

返り値は自動的にresNに入ります。res0にByteBufferが確保できたので、以後この値を使います。
変数に入ると同時にStringとして表示してくれるのが、特に今回は有難いところです。position/limit/capacityの値がすぐ確認できます。

scala> res0.putInt(123)
res1: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=4 lim=128 cap=128]

scala> res0.putInt(456)
res2: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=8 lim=128 cap=128]

値を置くと勝手にpositionが進んでいますね。

scala> res0.putInt(0, 789)
res3: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=8 lim=128 cap=128]

index指定のput系ではposition変わらず。

scala> res0.position()
res4: Int = 8

scala> res0.position(4)
res5: java.nio.Buffer = java.nio.HeapByteBuffer[pos=4 lim=128 cap=128]

position()で現在のpositionを得られます。(scalaなので()要らないけど、しばらくはJava的表記)
引数ありのpositionを使うと、positionを動かせます。

scala> res0.getInt()
res6: Int = 456

scala> res0
res7: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=8 lim=128 cap=128]

scala> res0.getInt(4)
res8: Int = 456

scala> res0
res9: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=8 lim=128 cap=128]

getInt()でもpositionが自動的に移動。やはりindex指定ならばposition変わらず。

scala> res0.rewind()
res10: java.nio.Buffer = java.nio.HeapByteBuffer[pos=0 lim=128 cap=128]

rewindで先頭に巻き戻ります。でも返るのがBufferなので、若干びびります。

scala> res0.limit(4)
res11: java.nio.Buffer = java.nio.HeapByteBuffer[pos=0 lim=4 cap=128]

scala> res0.putFloat(1.5f)
res12: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=4 lim=4 cap=128]

scala> res0.putFloat(2.6f)
java.nio.BufferOverflowException

limitもpositionと同じような引数あり・なし版があり。limitより先に書きこもうとすると、溢れた扱いになります。

実行例がやたらと長くなりましたが、とにかくjavaいじるのにscala便利だぜってことで。

ついでにDatagramChannel

scala> import java.nio.channels.DatagramChannel
import java.nio.channels.DatagramChannel

scala> import java.net._
import java.net._

scala> val ch = DatagramChannel.open(StandardProtocolFamily.INET)
ch: java.nio.channels.DatagramChannel = sun.nio.ch.DatagramChannelImpl@11045dfe

resNが複数あると紛らわしいのでval chに入れてみた。
(Javaっぽく書こうとしていたことは忘れる。importのワイルドカードもちょっとJavaと異なる)

scala> res0.limit(100)
res16: java.nio.Buffer = java.nio.HeapByteBuffer[pos=4 lim=100 cap=128]

scala> ch.send(res0, new InetSocketAddress("localhost", 10000))
res17: Int = 96

scala> res0
res18: java.nio.ByteBuffer = java.nio.HeapByteBuffer[pos=100 lim=100 cap=128]

sendの返り値は、送信したByte数です。positionからlimitまで送信、そしてpositionは移動。
(自動インクリメントのputでデータを配置したあと、positionを巻き戻し忘れると…)