HOWTO · Java

Java のバイト バッファ クラス

Java の ByteBuffer の位置、制限、容量がバイトの読み取りと書き込みをどのように制御するか、バッファの作成方法と使用方法、そして 2 つの Java の例が getChar() でバイトと文字を読み取る方法を学びます。

このページの内容

ByteBuffer はバイトを格納し、読み取りまたは書き込みの次の位置を追跡します。その位置、制限、容量によって、操作がアクセスできるバイトが決まります。このガイドでは、それらの状態変化、主な作成メソッドとアクセスメソッド、そして 2 つの自己完結型の Java の例(バイト値用と文字用)を説明します。

Java の ByteBuffer クラス

java.nio.ByteBuffer は、Java コードと I/O チャネルの間でバイナリデータを移動するために使用される、バイト指向の NIO バッファです。基本データとして一連の整数を格納するわけではありません。putInt() のようなメソッドは、バッファのバイト順を使用してプリミティブ値をバイトにエンコードします。

バッファには 3 つの重要な状態値があります。

  • 容量はバッファの固定サイズです。
  • 位置は、相対的な読み取りまたは書き込みが使用する次のバイトのインデックスです。
  • 制限は、読み取りも書き込みもできない最初のインデックスです。最大で容量です。

allocate() による新しいバッファは、位置が 0、制限が容量と等しく、書き込まれたデータがありません。相対的な put() は位置を進めます。flip() を呼び出すと、制限が現在の位置に設定され、位置が 0 にリセットされ、書き込まれたバイトが読み取り用に準備されます。相対的な get() は読み取りながら位置を進めます。clear() は、制限を容量に、位置を 0 に戻すことで、バッファ全体を別の書き込み用に準備します。バイトは消去されません。rewind() は現在の制限を維持しながら位置を 0 にリセットするので、同じ読み取り可能な領域を再び読むことができます。

クラス階層は Object → Buffer → ByteBuffer です。バッファはダイレクトまたは非ダイレクトのいずれかです。allocateDirect() は通常の Java ヒープ外のストレージを要求し、一部の I/O パターンで役立つことがありますが、自動的に速くなるわけではありません。ダイレクトストレージがアプリケーションの測定された I/O ニーズに合う場合を除き、allocate() を優先してください。

Java の ByteBuffer クラスの操作カテゴリ

ByteBuffer の操作はいくつかの実用的なグループに分類されます。

  • 作成: allocate(capacity) はヒープバッファを作成し、allocateDirect(capacity) はダイレクトバッファを要求し、wrap(byteArray) は既存のバイト配列をバッファを通じて公開します。
  • 相対アクセス: get() と put(byte) は現在の位置を使用し、それを進めます。それらのバルクオーバーロードは複数のバイトを転送し、転送された数だけ進みます。
  • 絶対アクセス: get(index) と put(index, value) は位置を変更せずに特定のインデックスを指定します。インデックスは依然として現在の制限内になければなりません。
  • プリミティブ値とビュー: getInt()、putInt(value)、getChar()、putChar(value) はプリミティブ値とバイトの間で変換します。order() 設定は、これらの複数バイト値のバイト順を決定します。asIntBuffer() のようなビューは、元のバッファの位置を変更せずにバイトを整数として解釈します。
  • 状態と派生バッファ: position()、limit()、remaining()、slice()、duplicate()、compact()、rewind() により、コードは読み取り可能および書き込み可能な領域を検査または管理できます。

wrap() メソッドファミリーは、既存の配列に支えられたバッファを作成します。ByteBuffer.wrap(byte[]) は整数ではなくバイトをラップします。int[] をラップするには、IntBuffer.wrap(intArray) を使用します。これは整数バッファを作成し、異なる API です。バイト配列は ByteBuffer を通じてバイトのシーケンスとして見ることもでき、そのバイト順に従ってプリミティブ値として解釈できます。

Java のバイト バッファ クラスのメソッド

次のメソッドは、一般的な作成、状態、データアクセスのタスクをカバーします。

Method Purpose
allocate(int capacity) 要求された容量のヒープ ByteBuffer を作成します。
allocateDirect(int capacity) ダイレクト ByteBuffer を作成します。割り当てはヒープストレージとは異なるコストがかかる場合があります。
wrap(byte[] array) 提供されたバイト配列に支えられたバッファを作成します。
capacity() 固定容量を返します。
position() / position(int) 次の相対アクセスインデックスを読み取るか設定します。
limit() / limit(int) アクセス可能な領域の終わりを読み取るか設定します。
remaining() limit() - position() を返します。
get() / put(byte) 現在の位置で 1 バイトを読み取るか書き込み、それを進めます。
get(int) / put(int, byte) 位置を変更せずにインデックスで読み取るか書き込みます。
getInt() / putInt(int) and getChar() / putChar(char) 現在のバイト順を使用してプリミティブ値を読み取るか書き込みます。
order() / order(ByteOrder) 複数バイトのプリミティブ値に使用されるバイト順を読み取るか設定します。
hasArray() / array() アクセス可能なバッキングバイト配列を確認し、返します。ダイレクトバッファと読み取り専用バッファは公開しない場合があります。
isDirect() バッファがダイレクトかどうかを報告します。
flip() / clear() / rewind() 読み取り、書き込み、再読み取りのために位置と制限を変更します。
slice() / duplicate() / compact() ビューを作成するか、バッファの状態を関連ビューにコピーするか、残りのバイトを先頭に移動してさらに書き込めるようにします。
asIntBuffer() バッファの残りのバイトに対する整数ビューを作成します。

API の詳細については、Java ByteBuffer ドキュメントを参照してください。

Java でのバイト バッファの実装

この自己完結型の例は、2 つのバイト値を書き込み、flip() でバッファを読み取りモードに切り替えてから、値を読み取ります。

この完全なプログラムは、4 バイトのバッファを割り当て、2 つの値を書き込み、読み取り用に反転し、値と現在の状態を出力します。

import java.nio.ByteBuffer;

public class ByteBufferBytes {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.put((byte) 20);
        buffer.put((byte) 40);
        buffer.flip();

        byte first = buffer.get();
        byte second = buffer.get();
        System.out.println("bytes=" + first + "," + second);
        System.out.println("position=" + buffer.position()
                + ", limit=" + buffer.limit()
                + ", capacity=" + buffer.capacity());
    }
}

期待される出力:

bytes=20,40
position=2, limit=2, capacity=4

2 回の読み取りの後、位置は 2 です。制限は 2 のままで、容量は 4 のままです。buffer.put(0, (byte) 7) のような絶対書き込みは、位置を進めずにインデックス 0 のバイトを変更します。相対書き込みは現在の位置を使用し、それを進めます。位置または制限の設定はバッファの現在の状態によって制約されるため、位置をそれを超えて移動する前に制限を設定してください。remaining() は、位置と制限の間で利用可能なバイト数を報告します。

Java でバイト バッファ クラスの getChar メソッドを実装する

getChar() は現在の位置で 2 バイトを読み取り、位置を 2 つ進めます。バッファのデータ形式が特定の順序を必要とする場合は、バイト順を明示的に設定してください。この例は、putChar() で 4 つの Java char 値を書き込み、バッファを反転し、少なくとも Character.BYTES が残っている間だけ読み取ります。これは既知のバイト数を使用して安全に停止します。ゼロ文字を終端文字として依存するわけではありません。

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class ByteBufferChars {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(4 * Character.BYTES)
                .order(ByteOrder.BIG_ENDIAN);
        buffer.putChar('J');
        buffer.putChar('A');
        buffer.putChar('V');
        buffer.putChar('A');
        buffer.flip();

        StringBuilder text = new StringBuilder();
        while (buffer.remaining() >= Character.BYTES) {
            text.append(buffer.getChar());
        }
        System.out.println(text);

        buffer.rewind();
        char first = buffer.getChar();
        System.out.println("first=" + first);
    }
}

期待される出力:

JAVA
first=J

Java の char は UTF-16 コードユニットであり、getChar() は任意の UTF-8 バイトシーケンスをデコードしません。UTF-8 として格納されたテキストについては、バイトを読み取り、StandardCharsets.UTF_8 のような Charset でデコードしてください。この固定幅の例では、バッファサイズは文字あたりちょうど 2 バイトであり、残りバイトのチェックにより、各 getChar() に 2 バイトが利用可能であることが保証されます。次に rewind() により同じ領域が再び読み取り可能になり、最後の読み取りで最初の文字を取得できます。