メモリー・レイアウトおよび構造化アクセス

基本の操作のみを使用して構造化データにアクセスすると、保守が困難で理解しにくいコードになる可能性があります。かわりに、メモリー・レイアウトを使用すると、より複雑なネイティブ・データ型(C構造体など)を効率よく初期化してアクセスすることができます。

たとえば、次のC宣言について考えてみます。これはPoint構造体の配列を定義し、各Point構造体にはPoint.xPoint.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オブジェクトを返します。各構造体レイアウトには、xyという名前の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);
            }