配列以上,コレクション以下
図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となっています。
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の使い方を見ていきます。
著者紹介 櫻庭祐一 横河電機の研究部門に勤務。同氏のJavaプログラマ向け情報ページ「Java in the Box」はあまりに有名 |
最初のリストの「// ヒープ外にサイズが・・・」でメソッド名をallocateとしていましたが,allocateDirectです。お詫びして訂正します。本文は修正済みです。 [2010/02/15 16:00]