2013年9月6日金曜日

【Java】ReaderとかInputStreamとか。

なんとなくで使ってたけど
その真意はつまづいてみないと分からない
という話.

HTTP接続したクライアントから送られてくる専用コマンドを
処理していくサーバーなのだが,
その流れてくるデータストリームの扱いに難儀した.
当初はGETコマンドの実装から始めていたので,
改行も取れるBufferedReaderでストリームを処理していた.
ちゃんと文字列でデータをくれるし,ヘッダの処理も楽.

つまづいたのが,POST,特にバイナリを受信する時だった.
画像バイナリを受信して,ファイルに書き出してみると
ところどころデータが化けている.
理由はBufferedReaderで得たchar配列をbyte配列に
キャストするときに起こっていた.

Javaのcharって2バイトなのな.
(signed)charって1バイトだろ.
#typedef unsigned charがbyteだろ.
とか思ってたけど言語によって違うんだな.

なるほど,BufferedReaderではバイナリを処理するのは厳しい
ということで,InputStreamで処理させよう.
でも他のコマンドの実装ができてるからヘッダはBufferedReaderで.

とかやってると,今度は化けることはなくなったものの,
バイナリの先頭部分がごっそりなくなっている.
BufferedReaderでバイト数指定してreadした直後に
InputStreamでreadしてるのに!
データストリームから取りこぼし?

これはBufferedReaderの動作原理を知っていれば
あたりまえだろ
というような動作である.
BufferedReaderのBufferedというのは
readメソッドでチマチマ1バイトずつ処理しようが,
先にバッファー領域にごそっとストリームを格納する.

ファイルアクセスで考えるとイメージしやすいか.
ファイルの読み込みを行うときに
1バイトずつ処理するのとして,
1バイト読み込み→1バイト処理→1バイト読み込み→1バイト処理→…
とやると1バイトずつファイルアクセスが発生して
とてつもなく遅くなる.
そのため,BufferedReaderではバッファ領域に
ファイルを(デフォルト値で8KB程度?)読み込んで
それをreadメソッドで読むという流れになっている.
こうすることで使用感はInputStreamなどと同じなのに
ファイルアクセス回数を減らしている.

つまり,ヘッダだけ読み込もうとBufferedReaderに
データストリームを任せた瞬間に
BufferedReaderのバッファ領域にはヘッダと一緒に
バイナリファイルの先頭部分も格納されていた.
InputStreamがデータストリームを読みに行こうとした時には
バイナリの途中からになっていた,ということである.
バイナリの先頭部分はBufferedReaderの破棄と共に
闇に消えたのである.

つまりは,Reader系で作ったのがそもそもの間違いであり,
複数のストリームクラスを併用しようとしたのも間違いだった.

結局,実装済部分も含めて
BufferedInputStreamだけで処理するように作りなおした.

0 件のコメント:

コメントを投稿