ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

手作りネットプロトコル工房コミュのJava の ビット演算 シフト演算について

  • mixiチェック
  • このエントリーをはてなブックマークに追加
昨日は丸一日、BASE64のエンコードデコードを行うルーチンを書いていました。 僕が今書いているのは、書き込まれたデータをSAXを利用して読み込み、その中で必要があればBASE64のデコードを行うというもので、ファイルは数メガに及ぶ事があります。

普通のBASE64ルーチンは String型の引数を取って一括でデコードするようなものなのですが、10メガ近いBASE64文字列を一気に変換するのはちょっと無理があってメモリが足りなくなってしまうのです。 実は、今まではXERCESのものを拝借していたのですが、これでは機能が足りないということが判明したというわけです。

そういうことで、ストリーミングしながらデコードするようなものが必要だったので、自分で作りました。 思ったよりも手間取ってしまい、丸一日かかってしまいました。

というのも、処理自体は SAXの中から行われるので、当然デコード処理はプッシュ型で行う必要があるのです。しかし、一度デコード処理が走ってしまえば、こちらの処理はプルでしかデータを取りません。 しかも 渡すべき文字列データが大きければ、SAXは分割してプッシュしてきます。 というわけで、この間を取り持つ必要があるからです。 要するにパイプの様なものが必要ということがわかったわけです。

調べてみたら、PipedInputStream/ PipedOutputStream というパイプ処理するためのクラスが既に用意されていて、これを利用する事で間を取り持つ事ができるとのことでした。 これを使って、一つのBASE64デコード案件ごとにスレッドを起動して、見事マルチスレッドのコードルーチンが完成しました。

===========================

実は、最初、XERCESについていたアパッチ版のものを改造して作ろうかと思っていたのですが、これが、あんまりにもゴチャゴチャしていたので使うのをあきらめました。

とはいえ、ストリーミング処理でデコードをするようなサンプルも見当たらなかったため、一から自分で作る事にしました。 はずかしながら処理の仕方を知らなかったのですが、ここを見る限り、とても簡単そうでした。

http://en.wikipedia.org/wiki/Base64

こちらがアパッチ版です。
http://oka.nu/j/base64/apache/Base64.java

僕がスクラッチから書いた版は次のファイルです。
http://oka.nu/j/base64/ats/Base64.java

結構短くなりました。 中がまったく同じアルゴリズムのWriter/Reader版 と InputStream/OutputStream版の二つが含まれているので、実質は半分の長さです。

===========================

Javaには 符号無し型の変数がありません。 Javaはすべて符号ありで統一されているのです。これにより byte型 int型 が混在していても正しく負の数を含む計算を行うことができます。 シンプルさを目指したJavaの父・ゴスリングのセンスが光っています。

... が、こういうビット処理を行うときはこれが裏目に出ます。

更に事態を混乱させている事実が、Javaのシフト演算子は int型で行われる事です。

  byte b = (byte)0xff;
System.out.println( bb >>> 1 );

この結果は 普通に考えると0x7fになるのですが、なぜか 2147483647 になってしまうのです。 >>> 演算子は int型なので 一度32bit変換された後でシフト処理が行われるためです。 これは正直わかり辛いものがあります。

色々調べているうちにわかったのですが、Javaは 「キャストは切り捨て」「演算子は32bit」という一貫した法則で処理されているのだそうです。 これは シンプルですが、この法則を知らない者には大混乱を招くものではないでしょうか。

正しく処理を行うためには、一旦int型に変換し、0xffでマスク処理をした後で処理を行う必要があります。

つまり上記の例で言えば、

  byte b = (byte)0xff;
  System.out.println( ( b & 0xff ) >>> 1 );

というようにマスクをしてからシフトする必要があります。 8bit以上の領域から来るビットを消すためです。

同様に左シフトするときは

  byte b = (byte)0xff;
  System.out.println( ( b << 1 ) & 0xff );

とシフトしてからマスクする必要があります。 こちらは オーバーフローを切り捨てるためです。

注:Javaでは 0xffはbyte型ではなくint型となるため、0xffでマスクすると同時に自動的にint型に変換される...筈


また、byte型よりも大きなサイズを持つ値を byte型に変換する場合、自動的に切り捨てられることになっているのでマスクと同様な結果になります。 しかし byte型よりも大きな型に変換する場合は、該当する byteが負の数だと、int型の負の数として変換されるため、注意が必要になります。 特にシフト演算するときは暗黙のうちにint型に変換されるので、特に注意が必要です。

=============

正直このアパッチ版は、その辺の処理が結構混乱しているように見えます。 もちろん、このゴチャゴチャ感は、それだけが問題ではないのですが ... 最初に文字列全部をスキャンして空白を除いた文字列長を取得して後でもう一度空白を取り除く流れや、あちこちで配列をコピーしているところなど、 処理が直線的に流れておらず、あまり効率がよくないのではないかと思います。

何故なのか知らないのですが、アパッチ系のライブラリってソースが汚い事が多いようなきがします。 僕はJ2SEに付属しているソースを頻繁に読んでいますが、アパッチのソースとは比べ物にならない程きれいです。 Commonsはなかなか充実していて、しばしば使いたくなるのですが、実際使う段になるとクラスの分離が悪くて、いつも必要なクラスが芋づる式に出てきてしまい、使えない事が多いのです。

もちろん、あるものは積極的に使って、「車輪の再発明」を避ける努力はしなければなりませんが...。

コメント(0)

mixiユーザー
ログインしてコメントしよう!

手作りネットプロトコル工房 更新情報

手作りネットプロトコル工房のメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング