配列以上,コレクション以下

バッファのクラス構成
図1 バッファのクラス構成

バッファはプリミティブに特化したデータ・コンテナのクラスです。ArrayListクラスなどのコレクションとは異なり,オブジェクトを保持することはできないし,サイズを変更することもできません。また,バッファに異なる型の値を保持することもできません。

これらの機能の制限は,入出力に特化していることに起因しています。基本的に入出力ではバイトが読み書きできればいいので,この割り切りは潔いですね。

バッファの特徴を列挙しておきます。

  • プリミティブに限定したコンテナ
  • サイズ不変
  • 型の混合は不可
  • 基本的にシーケンシャル・アクセス(ランダム・アクセスも可能)
  • position,limit,capacityという三つのプロパティを持つ
  • ヒープ外のメモリーへの直接アクセスをサポート

バッファは,基底クラスとなるjava.nio.Bufferクラスと,intなどの型ごとに定義されている派生クラスから構成されています。図1にクラス構成を示しました。いずれのクラスもアブストラクト・クラスであり,ファクトリ・メソッドが用意されています。

バッファの生成

ファクトリ・メソッドには,空のバッファを作成するallocateメソッドと,配列を指定して作成するwrapメソッドがあります。

allocateメソッドの引数はバッファのサイズです。引数の型はintなので,バッファの最大サイズはInteger.MAX_VALUE = 231 - 1です。

allocateメソッドを使用して作成されたバッファは,ヒープ内に作成されます。これに対して,ByteBufferクラスだけはヒープ外のメモリーにアロケートすることが可能です。このためにはByteBuffer#allocateDirectメソッドを使用します。

  // ヒープ内にサイズが1024のByteBufferオブジェクトを作成
  ByteBuffer buffer = ByteBuffer.allocate(1024);
	
  // ヒープ外にサイズが2048のByteBufferオブジェクトを作成
  ByteBuffer directBuffer = ByteBuffer.allocateDirect(2048);

allocateメソッド,allocateDirectメソッドのいずれを使っても,作成されたButeBufferオブジェクトの使用方法は同じです。

wrapメソッドの引数は基となる配列です。このとき,配列がそのままバッファ内部のプロパティとして使用されます。したがって,バッファで要素の値を変更すると,基の配列も値が変更されてしまいます。

また,CharBufferクラスに限定されますが,CharSequenceオブジェクトを引数に取ることができます。StringクラスはCharSequenceインタフェースをインプリメントしているので,文字列を直接,引数に取ることができます。

  // 配列xを基にByteBufferオブジェクトを作成
  byte[] x = ...    // xを作成
  ByteBuffer buffer = ByteBuffer.wrap(x);
	
  // 直接,文字列を指定してCharBufferオブジェクトを作成
  CharBuffer charBuffer = CharBuffer.wrap("Hello, World!");

三つのプロパティ

このようにして作成されたバッファですが,基本的にはシーケンシャル・アクセスで使用します。シーケンシャル・アクセスを行うために,バッファは三つのプロパティを持っています。

  • capacity
  • position
  • limit

capacityはバッファのサイズを表しています。「ByteBuffer.allocate(10);」で生成したバッファであれば,capacityは10になります。

positionはシーケンシャル・アクセスでどこまで読み書きしたかという位置を示します。また,どこまで読み書きできるかを指定するのがlimitです。

これらの三つのプロパティのうち,capacityは不変ですが,positionとlimitは変更できます。ただし,常にposition <= limit <= capactityという関係を保たなければなりません。例えば,position > limitとなるような位置にpositionを移動してしまうと,IllegalArgumentException例外が発生します。

初期状態ではposition = 0,limit = capacityとなっています。

Buffer初期状態

positionやlimitを操作するメソッドとして,次のようなものがあります。

メソッド名 機能
position(int newPosition) positionをnewPositionに移動させる
limit(int newLimit) limitをnewLimitに移動させる
clear() position = 0,limit = 0に移動させる
flip() limitをpositionの位置に移動させ,positionを0にする
rewind() limitはそのままで,position = 0に移動させる
remaining() 現在のpositionからlimitまでの要素数を返す

これらのメソッドは来週,チャネルとともに使ってみます。

バッファへのアクセス

バッファに対する読み込みはgetメソッド,書き込みはputメソッドに統一されています。シーケンシャル・アクセスを行う場合,positionの位置から読み書きが行われ,読み書きを行った要素数だけpositionが進みます。

まずは読み込みです。

  ByteBuffer buffer = ...
 
  // 1バイトの読み込み
  // positionが1進む
  byte b;
  b = buffer.get();
 
  // 3バイトの読み込み
  // positionが3進む
  byte bs = new byte[3];
  buffer.get(bs);

バッファの読み込み

position がlimit以上になるような読み込みの場合,BufferUnderflowExcepton例外が発生します。この例外が発生したときには,positionの変化はありません。

ByteBufferクラスだけは,getメソッド以外にgetCharメソッド,getIntメソッド,getShortメソッド,getLongメソッド,getFloatメソッド,getDoubleメソッドが定義されています。これらのメソッドではそれぞれの型に応じたバイト数だけpositionが移動します。例えば,getIntメソッドの場合,intは4バイトなのでpostionも4移動します。

次に書き込みです。

書き込みはputメソッドを使用します。書き込みを行った要素数分だけpositionが移動します。

  ByteBuffer buffer = ...
 
  // 1バイトの書き込み
  // positionが1進む
  byte b = (byte)10;
  buffer.put(b);
 
  // 3バイトの書き込み
  // positionが3進む
  byte bs = {11, 12, 13};
  buffer.put(bs);

バッファの書き込み

また,putメソッドにはバッファを引数に取るものもあります。このとき,書き込まれるのは引数のバッファのpositionからlimitの間までです。

  ByteBuffer src = ByteBuffer.allocate(10);
 
  // srcはcapacityが10
  // positionが5,limitが8とする
  buffer.put(src);

この場合,bufferのpositionは8 - 5 = 3なので,3進んで7になります。ちなみにこの操作の後,srcのpositionはlimitの位置,つまり8になります。

バッファの書き込み

書き込みの場合でも,positionがlimit以上になるような書き込みを行うと,BufferOverflowExcepton例外が発生します。

また,これも読み込みの場合と同じですが,ByteBufferクラスだけは,putメソッド以外にputCharメソッド,putIntメソッド,putShortメソッド,putLongメソッド,putFloatメソッド,putDoubleメソッドが定義されています。これらのメソッドを使った場合のpostionの移動も読み込みの場合と同じで,それぞれの型を構成するバイト数だけ移動します。

バッファを使いこなすには,positionとlimitの使い方がカギになります。今週はpositionだけでしたが,来週はバッファとチャネルを組み合わせて使うことで,より具体的にpositionとlimitの使い方を見ていきます。

第4回を読む

著者紹介 櫻庭祐一

横河電機の研究部門に勤務。同氏のJavaプログラマ向け情報ページ「Java in the Box」はあまりに有名


■変更履歴
最初のリストの「// ヒープ外にサイズが・・・」でメソッド名をallocateとしていましたが,allocateDirectです。お詫びして訂正します。本文は修正済みです。 [2010/02/15 16:00]