メモリー・レイアウトおよび構造化アクセス
基本の操作のみを使用して構造化データにアクセスすると、保守が困難で理解しにくいコードになる可能性があります。かわりに、メモリー・レイアウトを使用すると、より複雑なネイティブ・データ型(C構造体など)を効率よく初期化してアクセスすることができます。
たとえば、次のC宣言について考えてみます。これはPoint
構造体の配列を定義し、各Point
構造体にはPoint.x
とPoint.y
という2つのメンバーが含まれます:
struct Point {
int x;
int y;
} pts[10];
このようなネイティブ配列は、次のように初期化およびアクセスできます:
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment =
arena.allocate((long)(2 * 4 * 10), 1);
for (int i = 0; i < 10; i++) {
int xValue = i;
int yValue = i * 10;
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2), xValue);
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2) + 1, yValue);
}
for (int i = 0; i < 10; i++) {
int xVal = segment.getAtIndex(ValueLayout.JAVA_INT, (i * 2));
int yVal = segment.getAtIndex(ValueLayout.JAVA_INT, (i * 2) + 1);
System.out.println("(" + xVal + ", " + yVal + ")");
}
}
Arena::allocateメソッドのコールの最初の引数によって、配列に必要なバイト数が計算されます。MemorySegment::setAtIndexメソッドのコールの引数によって、Point
構造体の各メンバーに書き込むためのメモリー・アドレスのオフセットが計算されます。同様の引数は、MemorySegment::getAtIndexメソッドに対して同じ計算を実行します。これらの計算を回避するには、メモリー・レイアウトを使用できます。
Point
構造体の配列を表すために、次の例ではシーケンス・メモリー・レイアウトを使用します:
try (Arena arena = Arena.ofConfined()) {
SequenceLayout ptsLayout
= MemoryLayout.sequenceLayout(10,
MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")));
VarHandle xHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("x"));
VarHandle yHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("y"));
MemorySegment segment = arena.allocate(ptsLayout);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xValue = i;
int yValue = i * 10;
xHandle.set(segment, 0L, (long) i, xValue);
yHandle.set(segment, 0L, (long) i, yValue);
}
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xVal = (int) xHandle.get(segment, 0L, (long) i);
int yVal = (int) yHandle.get(segment, 0L, (long) i);
System.out.println("(" + xVal + ", " + yVal + ")");
}
}
最初の文によって、SequenceLayoutオブジェクトで表されるシーケンス・メモリー・レイアウトが作成されます。これには、StructLayoutオブジェクトで表される10個の構造体レイアウトのシーケンスが含まれます。メソッドMemoryLayout::structLayoutは、StructLayoutオブジェクトを返します。各構造体レイアウトには、x
とy
という名前の2つのJAVA_INT値レイアウトが含まれます:
SequenceLayout ptsLayout
= MemoryLayout.sequenceLayout(10,
MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")));
事前定義値ValueLayout.JAVA_INTには、Java int値に必要なバイト数に関する情報が含まれます。
次の文によって、メモリー・アドレス・オフセットを取得する2つのメモリー・アクセスVarHandleが作成されます。VarHandleは、動的な強い型指定の参照であり、変数を参照するか、パラメータによって定義された変数ファミリ(静的フィールド、非静的フィールド、配列要素、オフヒープ・データ構造体のコンポーネント)を参照します。
VarHandle xHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("x"));
VarHandle yHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("y"));
メソッドPathElement.sequenceElement()は、シーケンス・レイアウトからメモリー・レイアウトを取得します。この例では、構造体レイアウトの1つをptsLayout
から取得します。メソッド・コールPathElement.groupElement("x")は、x
という名前のメモリー・レイアウトを取得します。名前が付いたメモリー・レイアウトを作成するにはwithName(String)メソッドを使用します。
for
文によって、VarHandle::setおよびVarHandle::getがコールされ、MemorySegment::setAtIndexおよびMemorySegment::getAtIndexのようにメモリーにアクセスします。
MemorySegment segment = arena.allocate(ptsLayout);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xValue = i;
int yValue = i * 10;
xHandle.set(segment, 0L, (long) i, xValue);
yHandle.set(segment, 0L, (long) i, yValue);
}
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xVal = (int) xHandle.get(segment, 0L, (long) i);
int yVal = (int) yHandle.get(segment, 0L, (long) i);
System.out.println("(" + xVal + ", " + yVal + ")");
}
この例では、set
メソッドは4つの引数を使用します:
segment
: 値を設定するメモリー・セグメント0L
: ベース・オフセット。配列の先頭を指すlong
座標です。(long) i
: 値を設定する配列索引を示す2番目のlong
座標xValue
およびyValue
: 設定する実際の値
VarHandle xHandle
およびyHandle
は、Point
構造体のサイズ(8バイト)とそのint
メンバーのサイズ(4バイト)を認識しています。つまり、setAtIndexメソッドのように、配列の要素またはメモリー・アドレス・オフセットに必要なバイト数を計算する必要はありません。
ヒント:
ベース・オフセットを使用すると、VarHandleに追加のオフセット計算を挿入することで、複雑なアクセス操作を表現できます。特に、メモリー・セグメントとベース・オフセットを使用して、可変長配列をモデル化できます。これらは、サイズが静的に認識されず、シーケンス・レイアウトを使用して表すことができない配列です。このようなメモリー・セグメントには、MemoryLayout::arrayElementVarHandleメソッドを使用してアクセスできます。例については、MemoryLayoutインタフェースのJavaDoc APIドキュメントの可変長配列の操作に関する項を参照してください。