メモリー・レイアウトおよび構造化アクセス
基本の操作のみを使用して構造化データにアクセスすると、保守が困難で理解しにくいコードになる可能性があります。かわりに、メモリー・レイアウトを使用すると、より複雑なネイティブ・データ型(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++) {
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2), i); // x
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2) + 1, i); // y
}
// ...
}
Arena::allocateメソッドのコールの最初の引数によって、配列に必要なバイト数が計算されます。MemorySegment::setAtIndexメソッドのコールの引数によって、Point
構造体の各メンバーに書き込むためのメモリー・アドレスのオフセットが計算されます。これらの計算を回避するには、メモリー・レイアウトを使用できます。
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++) {
xHandle.set(segment, (long) i, i);
yHandle.set(segment, (long) i, i);
}
// ...
}
最初の文によって、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がコールされ、MemorySegment::setAtIndexのようにメモリーにアクセスします。この例では、値(3番目の引数)がメモリー・セグメント(最初の引数)のインデックス(2番目の引数)に設定されます。VarHandle xHandle
およびyHandle
は、Point
構造体のサイズ(8バイト)とそのint
メンバーのサイズ(4バイト)を認識しています。つまり、setAtIndexメソッドのように、配列の要素またはメモリー・アドレス・オフセットに必要なバイト数を計算する必要はありません。
MemorySegment segment = arena.allocate(ptsLayout);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
xHandle.set(segment, (long) i, i);
yHandle.set(segment, (long) i, i);
}