New I/O バッファの使い方

New I/O バッファとは?

低レベル I/O が望ましい場合、例えば OpenGL ライブラリを利用する時にも NIO Buffer は利用されます。ここでは具体的な利用例と、それぞれのコードの意味をみてみましょう。

NIO バッファにはダイレクトバッファ (direct buffer) と非ダイレクトバッファ (non-direct buffer) があります。

ダイレクトバッファでは、 Java VM は可能な限りネイティブ I/O 操作を実行するよう試みます。 これによって、中間のバッファの割り当てを抑えます。

allocateDirect メソッド等で割り当てたバッファは ダイレクトバッファになります。通常ダイレクトバッファの割り当て、解放のコストは、そうではない場合より高くなります。

このため短時間で割り当て、解放を繰り返すような操作には向きません。サイズが大きく、生存時間の長いバッファの割り当てにのみダイレクトバッファを利用することが望ましいです。

New I/O バッファの利用例

では実際に動くコードを見ながら、New I/O バッファの利用例と内容を説明します。

package com.keicode.java.test;

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

public class TestApp {
  public static void main(String[] args) {

    float[] points = new float[]{
        -2f, -1f, 0f, 1f, 2
    };

    ByteBuffer byteBuffer
        = ByteBuffer.allocateDirect(points.length * 4);

    // バイトオーダーを表示
    System.out.println("Buffer: " + byteBuffer.order());
    System.out.println("Native: " + ByteOrder.nativeOrder());

    // バイトオーダーの設定
    byteBuffer.order(ByteOrder.nativeOrder());

    // ビューバッファの作成
    FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
    floatBuffer.put(points);
    floatBuffer.position(0);

    // 内容の表示
    while (0 < floatBuffer.remaining()) {
      System.out.println(floatBuffer.get());
    }
  }
}

この結果は次のようになりました。

Buffer: BIG_ENDIAN
Native: LITTLE_ENDIAN
-2.0
-1.0
0.0
1.0
2.0

さて、それではそれぞれ何をしたか説明します。

Java のダイレクトバイトバッファの割り当て

まずはテストデータとして float 型の配列を作成します。要素は5つで、 -2 から 2 までです。

次に ByteBuffer.allocateDirect メソッドを利用してダイレクトバッファを割り当てます。 float はプリミティブデータタイプのひとつで 4 バイトと決まっているので、 「配列の要素数」 かける 「float のサイズ」 で、 何バイトのバッファを割り当てれば良いかわかります。

Java のダイレクトバッファのバイトオーダーの設定

この段階でバイトバッファに設定されているバイトオーダーと、このシステムが実行しているプラットフォームのバイトオーダーを確認します。

ByteBuffer のデフォルトのバイトオーダーはビッグエンディアンです。 一方、ネイティブのバイトオーダーはその実行環境に依存しています。ここではリトルエンディアンでした。

プラットフォームネイティブのバイトオーダーにデータをレイアウトするほうが実行効率的に望ましいので、 order メソッドでバッファのバイトオーダーをネイティブのそれに揃えます。

ビューバッファを作成しバッファにインデックスアクセス

さて、ネイティブの元のバッファがバイト単位のバッファであると、そこで扱うデータのタイプによって扱いが変わってしまいます。

例えば、 float なら 4 バイト毎にデータが配置されますが、 short なら 2 バイト毎に配置されます。この違いによりバイトバッファを直接扱うと、それぞれのデータへのアクセスが煩雑になります。

この問題を解決するために、 ビューバッファ (view buffer) というのを作ります。

asFloatBuffer() メソッドで float を扱うためのビューバッファを作ると、そのバッファ内の float データへ単純なインデックスアクセスが可能になります。

もとのバッファがダイレクトバッファであれば、ビューバッファもダイレクトバッファになります。また、元のバッファへの変更はビューバッファにも反映し、その逆にビューバッファへ行った変更も元のバッファに反映されます。

ただし、ビューバッファのバイトオーダーは、それを作成したタイミングの元のバッファのそれに固定されます。そのため、asFloatBuffer を呼ぶ前に order メソッドでバイトバッファのバイトオーダーを指定しておきます。

データの設定と読み取り

ここではデータの設定は put メソッドで行っています。その後、現在の読み取り一を position(0) で設定し、 get メソッドで要素を読み込んでいます。

get メソッドはデータを読み込み、現在の位置をひとつ進めます。

また、ここでは残りのデータの有無はビューバッファの remaining メソッドを利用しています。

以上で、New I/O のダイレクトバッファの使用例について説明しました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Java 入門