Movatterモバイル変換


[0]ホーム

URL:


S-JIS[2009-01-16]変更履歴

Buffer(ByteBuffer・CharBuffer)

JDK1.4で新設されたnew I/O関連に、バッファークラスがある。
でもちょっと癖があって分かりづらい(苦笑)


概要(特徴)

Bufferの用途・存在意義は、「バッファー」という名前が示す通り、データを保存(追加)し、そこから取得する為のもの。ではあるのだけれど。

Bufferを使う上でややこしいのが、そのBufferインスタンスを今「書き込みに使用しているのか」「読み取りに使用しているのか」をプログラマーが把握・意識して、それに応じた使い方(メソッド呼び出し)をしなければならない(制御する必要がある)こと。
また、同一メソッドでも、読み取りの場合と書き込みの場合で意味合いが異なるものがある。→メソッド一覧

java.nio.Bufferは全てのバッファーの親である抽象クラスであり、データ型に応じて実装クラスがある。
例えばbyteを扱うのはByteBuffercharを扱うのはCharBuffer
ただし、実際にはさらにByteBufferやCharBufferを継承したクラス(publicでない)が使われる。


データ例

Bufferは、position・limit・capacityというパラメーターを持っている。
capacityは、バッファーサイズ(上限)。不変。
positionは、バッファー内での読み書きをする開始/現在位置。
limitは、書き込みの場合、書き込める最大サイズ(書き込める上限)。読み取りの場合、保持しているデータサイズ(読み込める上限)。

capacityは変わらないが、positionやlimitは読み書き操作に応じて変わっていく。
書き込みも読み取りも、現在のpositionの位置から開始する。

 処理内容処理後のバッファー内の様子解説
1//バッファーインスタンスを生成する
ByteBuffer bb = ByteBuffer.allocate(16);
012345678910111213141516…位置
00000000000000000000000000000000 …データ
↑position↑capacity
↑limit
allocate()
バッファーを作成する。
positionは0、
limitはcapacityになる。
2//バッファーへ書き込む
bb.put((byte)0xaa);
012345678910111213141516…位置
aa000000000000000000000000000000 …データ
 ↑position↑capacity
↑limit
put()
positionの位置に書かれ、
書き込んだ分だけ
positionが動く。
もしlimitまで到達して
なお書き込もうとしたら、
オーバーフロー(例外発生)。
3//バッファーへ書き込む
bb.put((byte)0xbb);
bb.put((byte)0xcc);
bb.put((byte)0xdd);
bb.put((byte)0xee);
012345678910111213141516…位置
aabbccddee0000000000000000000000 …データ
 ↑position↑capacity
↑limit
4//読み込み状態に変更する
bb.flip();
012345678910111213141516…位置
aabbccddee0000000000000000000000 …データ
↑position↑limit↑capacity
flip()は、
limitをpositionの値に設定し
positionを0にする。
5//バッファーから読み取る
byte data = bb.get();
//dataの値は0xaa
012345678910111213141516…位置
aabbccddee0000000000000000000000 …データ
 ↑position↑limit↑capacity
get()
positionの位置から読まれ、
読み込んだ分だけ
positionが動く。
6//バッファーから読み取る
while (bb.hasRemaining()) {
 data = bb.get();
}
012345678910111213141516…位置
aabbccddee0000000000000000000000 …データ
 ↑limit
↑position
↑capacity
読取時のhasRemaining()は、
残りデータが有ればtrue。
positoin=limitになるとfalse。
7//バッファーをクリアする
bb.clear();
012345678910111213141516…位置
aabbccddee0000000000000000000000 …データ
↑position↑capacity
↑limit
clear()は、
positionを0、
limitをcapacityに戻す。
(データ自体は消されない)
82(書き込み開始)へ戻る
6'上記5の直後(データが残っている状態)で
//追加書き込みできる状態にする
bb.compact();
012345678910111213141516…位置
bbccddeeee0000000000000000000000 …データ
 ↑position↑capacity
↑limit
compact()
読まれなかったデータを
0の位置に移動させ、
positionを続きの位置に移す。
limitはcapacityに戻す。
7'//バッファーへ書き込む
bb.put((byte)0x11);
012345678910111213141516…位置
bbccddee110000000000000000000000 …データ
 ↑position↑capacity
↑limit
(データが追加される)

注意点

バッファー作成直後にいきなり読み取ろうと(get)すると、limitはcapacity位置なので、いきなり最大サイズまで読み込むことになってしまう。

また、書き込み(put)し終わった後flip()せずに読み込もうとすると、positionは書き込んだ直後の位置なのでそこから読み取りをスタートすることになり、書き込んだデータでなく初期化されている(あるいは以前書き込んだ)データをlimit位置(=capacity)まで読み込むことになってしまう。

同様に、全部読み終わった後clear()せずに書き込もうとすると、positionがlimit位置に居るのでそれ以上書き込めない。いきなりオーバーフローになってしまう。

読んだ後clear()でなくflip()したとすると、(limitにはpositionが代入されるので)limitは最後に読み込んだ直後の位置となり、positionは0になる。
この状態で書き込み(put)を始めると、前回読み込んだサイズ(=limit位置)までしか書き込めないという妙な事態になる。


Bufferのメソッド

Bufferには、以下のようなメソッドがある。
(中には、バッファーの実装特有(ByteBuffer・CharBuffer等の専用)のメソッドもある)

※「クラス」のBはBuffer、BBはByteBuffer、CBはCharBuffer
※「使用可能タイミング」とは、そのメソッドを呼び出すことに意味のある状態
※「実行後のモード」は、メソッド呼出し後に可能な、意味のある操作ができる状態

メソッドクラス使用可能
タイミング
説明(処理の内容)実行後のバッファーのモード
BBBCB
allocate(サイズ) 初期化バッファーインスタンスを生成する。書き込める状態になっている
allocateDirect(サイズ)  ByteBufferでは、Javaのヒープ外(マシンネイティブ)に領域を確保できる。
一般的に最初の領域確保と最後の解放にはコストがかかるが、読み書き速度は高速。
wrap(配列) 指定された配列内に読み書きする為のバッファーを生成する。
配列 = array()常に(JDK1.6以降)
バッファー内部でJavaの配列を保持している場合、その配列を返す。
その配列のデータを書き換えるとバッファー内にも影響がある。
共変戻り値型を使用しているので、Buffer#array()の戻り型はObjectだが、ByteBufferはbyte[]、CharBufferはchar[]を返す)
変更なし
put(データ)
put(配列)
 書込状態position位置にデータを書き、書き込んだ分だけpositionを加算する。
limitを超えたらオーバーフロー。
変更なし
(書き込める状態のまま)
append(データ)  JDK1.5で追加されたメソッド。
だが、中身はput()なので素直にput()を使った方がいいだろう。
flip()limitを現在のpositionに設定し、positionを0にする。
すなわち、今まで書き込んだデータを先頭から読めるようにする。
読み込める状態になる
変数 =get()
get(配列)
 読込状態position位置のデータを読み、読み込んだ分だけpositionを加算する。
limitを超えたらオーバーフロー。
変更なし
(読み込める状態のまま)
変数 =toString()CharBufferでは、char配列をStringにして返す。
この場合、positionやlimitは変更されない。
rewind()positionを0にする。limitは変わらない。
すなわち、データを先頭から再度読み込めるようになる。
clear()両方positionを0にし、limitをcapacityに戻す。
(内部で保持しているデータが00に初期化されるわけではない)
書き込める状態になる
compact() 読込状態読み取られずに残っているデータをバッファーの先頭に移動させて(詰めて)、positionをその直後にし、limitをcapacityに戻す。
すなわち、追加書き込みが出来るようになる。
remaining()書込状態limit - positionを返す。
すなわち、書き込める残りのサイズを返す。
変更なし
読込状態limit - positionを返す。
すなわち、読み取れる(残っている)データのサイズを返す。
hasRemaining()書込状態remaining()>0のときtrue。
すなわち、書き込みできる余地が残っているとき、真。
読込状態remaining()>0のときtrue。
すなわち、読み込めるデータがまだ残っているとき、真。
mark()両方現在のpositionを記憶(mark)する。reset()でその位置へ戻れる。変更なし
reset()記憶されていた位置(mark)へpositionを移動する。
mark()後にflip()clear()等のメソッドを呼ぶとmarkもクリアされるので、その場合は移動できない(例外が発生する)。
位置 = position()常に現在のpositionを返す。設定系の場合、値による
取得系の場合、変更なし
position(位置)positionをセットする。0≦位置≦limitでないとエラー(例外発生)。
位置 =  limit()現在のlimitを返す。
limit(位置)limitをセットする。0≦位置≦capacityでないとエラー(例外発生)。
positionがlimitより大きくなったら、positionはlimit位置に変更される。
サイズ = capacity()capacityを返す。

1文字ずつ文字コードを変換する例

文字コードを変換するCharsetDecoder#decode()CharsetEncoder#encode()は、入出力にバッファーを使用する。
(ファイルの文字コードを変換しつつ読み込むにはInputStreamReaderを使えばよいが、ここでは敢えて1文字ずつ変換するメソッドを自作してみる)

public static void main(String[] args) throws IOException {String encoding = "MS932";Charset cs =Charset.forName(encoding);FileInputStream fis = new FileInputStream("ファイル名");try {decode(fis, cs);} finally {fis.close();}}
public static voiddecode(InputStream is, Charset cs) throws IOException {CharsetDecoderdecoder = cs.newDecoder();booleanend = false; //ファイルの終わりまで読み込んだかどうか// バッファーを用意する。// 今回は1文字ずつしか変換しないので、そんなに大きなバッファーは要らないByteBuffer bb = ByteBuffer.allocateDirect(16);// bbは書き込める状態CharBuffer cb = CharBuffer.allocate(16);// cbは書き込める状態do {int d = is.read();if (d >= 0) {System.out.printf("%02x→", d);bb.put((byte) d);// bbへの書き込み} else {end = true;}bb.flip();// bbを読み込み状態に変更CoderResult cr =decoder.decode(bb, cb,end);// bbから読み込み、cbへ書き込むif (!cr.isUnderflow()) {cr.throwException();}if (end) {cr =decoder.flush(cb);// cbへ書き込むif (!cr.isUnderflow()) {cr.throwException();}}cb.flip();// cbを読み込み状態に変更while (cb.hasRemaining()) {// cbに読み込めるデータがある場合char c = cb.get();// cbから読み込みSystem.out.printf("[%c]%n", c);}if (bb.hasRemaining()) {// bbに読み込めるデータがまだ残っている場合bb.compact();// データを詰めて書き込める状態に変更} else {bb.clear();// 書き込める状態に変更}cb.clear();// cbを書き込み状態に変更} while (!end);}

実行例:

30→[0]31→[1]32→[2]33→[3]41→[A]42→[B]43→[C]61→[a]62→[b]63→[c]82→a0→[あ]82→a2→[い]82→a4→[う]8e→c0→[実]8c→b1→[験]

CharsetDecoder#decode()は、ByteBufferのデータを(解釈できる限り)読み込んで、解釈できた文字だけをCharBufferに書き込む。
今回は1バイトずつ渡しているので、2バイト(以上の)文字は1バイト目では解釈できない。
その場合はCharBufferには何も書かれず、ByteBufferにも解釈不能データが残る(読込位置のpositionがlimitまで行かない)。そこでcompact()を呼び出して追加書き込みできるようにしている。
bb.hasRemaining()を使って条件判断せずに 常にcompact()を呼び出しても状態は同じになるが、compact()は内部で配列コピーを呼び出すので、clear()の方が軽い(高速)。


ファイルチャネルからバッファーに読み込む例

JDK1.4以降のFileInputStream(やFileOutputStream)からはファイルチャネルを取得することが出来る。
そして、ファイルチャネルから読み出してByteBufferへ直接出力することが出来る。

public static void main(String[] args) throws IOException {String encoding = "MS932";Charset cs =Charset.forName(encoding);FileInputStream fis = new FileInputStream("ファイル名");FileChannelfch = fis.getChannel();try {decodeCh(fch, cs);} finally {fis.close(); // 取得したチャネルもクローズされる}}
public static voiddecodeCh(FileChannel ch, Charset cs) throws IOException {CharsetDecoder decoder = cs.newDecoder();boolean end = false; //ファイルの終わりまで読み込んだかどうか// バッファーを用意する。ByteBuffer bb = ByteBuffer.allocateDirect(256);// bbは書き込める状態CharBuffer cb = CharBuffer.allocate(256);// cbは書き込める状態do {int len =ch.read(bb);// bbへ書き込みif (len < 0) {end = true;}bb.flip();// bbを読み込み状態に変更CoderResult cr = decoder.decode(bb, cb, end);// bbから読み込み、cbへ書き込むif (!cr.isUnderflow()) {cr.throwException();}if (end) {cr = decoder.flush(cb);// cbへ書き込むif (!cr.isUnderflow()) {cr.throwException();}}cb.flip();// cbを読み込み状態に変更String str = cb.toString();System.out.println(str);bb.compact();// データを詰めて書き込める状態に変更cb.clear();// cbを書き込み状態に変更} while (!end);}

なお、ファイルチャネルには、ファイル内を直接バッファーとして取得するメソッドも用意されている。
(ついでにCharsetDecoder#decode()にもCharBufferを生成して返すメソッドが用意されている)

public static void decodeLump(FileChannel ch, Charset cs) throws IOException {CharsetDecoder decoder = cs.newDecoder();MappedByteBuffer bb =ch.map(MapMode.READ_ONLY, 0,ch.size());// bbは読み込める状態になっているCharBuffer cb = decoder.decode(bb);// bbから読み込んで、cbを返す// cbは読み込める状態になっているString str = cb.toString();System.out.println(str);}

Java目次へ戻る /新機能へ戻る /技術メモへ戻る
メールの送信先:ひしだま

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


[8]ページ先頭

©2009-2025 Movatter.jp