InputStream系のクラスは昔のマリオの横スクロールのように、一度進んでしまうと後戻りはできない仕様になっている。
BufferedInputStreamなら、markとresetを使って、条件付きで後戻りが可能になる。
当然、InputStream自体を巻き戻すことはできないので、戻れる範囲はバッファリングしている中のみ可能。
なので、markを呼び出す時に、バッファリングする上限を指定する必要がある。この時指定された値以上のサイズをreadしてしまうと、markは無効になりresetできなくなる。
例を書いてみる。相変わらずScalaだが心の目でJavaに(以下略
とりあえず、ファイルを読み込んで、markで26バイトだけマークしておいてと指定し、resetでposが戻ることを確認。その後、26バイトを超えて読み込んでresetしてみる。
// BufferedInputStreamの用意
import java.io.{ FileInputStream, BufferedInputStream }
val in = new BufferedInputStream(new FileInputStream("temp/test.txt"))
// temp/test.txtの内容は
// ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
// 26バイトだけマークしとけと命令
in.mark(26)
// 10バイト読み込んでみる
val buf = new Array[Byte](10)
in.read(buf, 0, 10)
println(new String(buf))
//=> ABCDEFGHIJ
// さらに10バイト読み込んでみる(続きを読むことになる)
in.read(buf, 0, 10)
println(new String(buf))
//=> KLMNOPQRST
// リセットしてみる
in.reset
// リセット後に10バイト読んでみると、ちゃんと戻ってる
in.read(buf, 0, 10)
println(new String(buf))
//=> ABCDEFGHIJ
// markで指定したlimit以降まで読み込んでみる
in.read(buf, 0, 10)
println(new String(buf))
//=> KLMNOPQRST
in.read(buf, 0, 10)
println(new String(buf))
//=> UVWXYZabcd
// markを超えている状態でresetしてみる
in.reset
// あれ、ちゃんと読めた
in.read(buf, 0, 10)
println(new String(buf))
//=> ABCDEFGHIJ
markで指定した26を超えて読みに行ったけど、普通にresetできてしまった。どうやらバッファサイズ(デフォルトは8196)を超えていない場合は、ちゃんと戻ってくれるらしい。
BufferedInputStreamのコード上では、「this.pos >= buffer.length」(読み込んでいるとこがバッファサイズを超えた時、つまりバッファが埋まって詰め替えが必要になった時)にだけ、markpos(マークしてある場所を保持してる)の削除をおこなってる。
というわけで、8196以上のサイズを読みに行けば、エラーになるはず。
まず、試しに8000バイトでうまく取れることを確認。
// BufferedInputStreamの用意
import java.io.{ FileInputStream, BufferedInputStream }
val in = new BufferedInputStream(new FileInputStream("temp/test.txt"))
// とりあえず10を指定しておく
in.mark(10)
// まず、8000バイトだけ読む
val buf1 = new Array[Byte](8000)
in.read(buf1, 0, 8000)
// resetして再度読んでみる(ちゃんとresetできてる)
in.reset
val buf2 = new Array[Byte](10)
in.read(buf2, 0, 10)
println(new String(buf2))
//=> ABCDEFGHIJ
次に9000バイトでやってみる。これは落ちるはず。
// 9000バイト読んで、resetしてみる
in.read(buf1, 0, 9000)
in.reset
//=> org.specs.runner.UserError: java.io.IOException: Resetting to invalid mark
よし、ちゃんとエラーになった。
という感じで、バッファの状態によって思ったよりresetできてしまう場合があるようだ。
次にmarkに10000を指定して、9000バイトでresetしてみると、ちゃんと9000でもresetできることが分かる。もちろんこの場合は10000を超えて読んだ後にresetするとエラーになる。
// BufferedInputStreamの用意
import java.io.{ FileInputStream, BufferedInputStream }
val in = new BufferedInputStream(new FileInputStream("temp/test.txt"))
// 10000を指定
in.mark(10000)
// まず、9000バイト読んで、resetしてみる
val buf1 = new Array[Byte](9000)
in.read(buf1, 0, 9000)
// resetして読むと、ちゃんと戻ってる
in.reset
val buf2 = new Array[Byte](10)
in.read(buf2, 0, 10)
println(new String(buf2))
//=> ABCDEFGHIJ
markであまり大きい値を指定するとバッファに溜め込んでメモリを消費することになるし、小さい値を指定するとresetできないこともある。
個人的にこれを何に使おうと思ったかというと、文字コード判定。
juniversalchardet(Javaの文字コード判別ライブラリ。Mozillaからのポーティング)でStreamの前の方だけ読んで文字コードを判別する処理を書こうと思ったんだけど、引数にInputStreamをもらった場合、最初の方だけ読み込んでしまうと、一部読み進んだ状態になってしまうわけで、よろしくない。
ので、引数をBufferedInpuStreamでもらって、最大で前方32768くらい読んで文字コードを判定して、resetした状態で処理を終了しようかと思った。