ファイル内のメモリー領域を使用したメモリー・セグメントのバッキング

ファイル内のメモリー領域は、メモリー・セグメントを支えることができます。これを行うには、FileChannel::mapメソッドを使用して、ファイル・チャネルの領域をメモリー・セグメントにマップします。

次の例は、「メモリー・レイアウトおよび構造化アクセス」で説明されているものと似ています。メモリー・セグメントが作成され、10個のPoint構造のネイティブ配列が移入されます。違いは、メモリー・セグメントがファイル・チャネル内の領域にマップされることです。この例では、point-array.dataという名前のファイルを作成し、それを移入します。次に、読取り専用ファイル・チャネルでpoint-array.dataを開き、FFM APIを使用してその内容を取得します。

public class MemoryMappingExample {
    
    static final SequenceLayout ptsLayout
        = MemoryLayout.sequenceLayout(10,
            MemoryLayout.structLayout(
                ValueLayout.JAVA_INT.withName("x"),
                ValueLayout.JAVA_INT.withName("y")));
            
    static final VarHandle xHandle
        = ptsLayout.varHandle(
            PathElement.sequenceElement(),
            PathElement.groupElement("x"));
            
    static final VarHandle yHandle
        = ptsLayout.varHandle(
            PathElement.sequenceElement(),
            PathElement.groupElement("y"));                        

    public static void main(String[] args) {
        MemoryMappingExample myApp = new MemoryMappingExample();
        try {
            Files.deleteIfExists(Paths.get("point-array.data"));
            myApp.createFile();
            myApp.readFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    void createFile() throws Exception {
        
        try (var fc = FileChannel.open(Path.of("point-array.data"),
                      Set.of(CREATE, READ, WRITE));
             Arena arena = Arena.ofConfined()) {
                 
            MemorySegment mapped = fc.map(READ_WRITE, 0L, ptsLayout.byteSize(), arena);
            
            System.out.println("Empty mapped segment:");
            System.out.println(toHex(mapped));

            for (int i = 0; i < ptsLayout.elementCount(); i++) {
                int xValue = i;
                int yValue = i * 10;
                xHandle.set(mapped, 0L, (long) i, xValue);
                yHandle.set(mapped, 0L, (long) i, yValue);
            }
            
            System.out.println("Populated mapped segment:");
            System.out.println(toString(mapped));
            System.out.println("Populated mapped segment in hex:");
            System.out.println(toHex(mapped));
        } 
    }
    
    
    void readFile() throws Exception {
        try (var fc = FileChannel.open(Path.of("point-array.data"),
                      Set.of(SPARSE, READ));
             Arena arena = Arena.ofConfined()) {
                 
            MemorySegment mapped = fc.map(READ_ONLY, 0L, ptsLayout.byteSize(), arena);
            
            System.out.println("Contents of point-array.data:");
            System.out.println(toString(mapped));
        }            
    }

    static String toString(MemorySegment seg) {
        String outputString = "";
            for (int i = 0; i < ptsLayout.elementCount(); i++) {
                int xVal = (int) xHandle.get(seg, 0L, (long) i);
                int yVal = (int) yHandle.get(seg, 0L, (long) i);
                outputString += "(" + xVal + ", " + yVal + ")";
                if ((i+1 != ptsLayout.elementCount())) outputString += "\n";
            }
        return outputString;
    }
    
    static String toHex(MemorySegment seg) {
        String outputString = "";
        HexFormat formatter = HexFormat.of();
        
        byte[] byteArray = seg.toArray(java.lang.foreign.ValueLayout.JAVA_BYTE);
                
        for (int i = 0; i < byteArray.length; i++) {
            outputString += formatter.toHexDigits(byteArray[i]) + " ";
            if ((i+1) % 8 == 0 && (i+1) % 16 != 0) {
                outputString += " ";
            }
            if ((i+1) % 16 == 0 && (i+1) < byteArray.length) {
                outputString += "\n";
            }    
        }
        return outputString;
    }    
}

ヒント:

パフォーマンス上の理由から、VarHandlesをstatic finalとして宣言することをお薦めします。

次のように出力されます。

Empty mapped segment:
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
Populated mapped segment:
(0, 0)
(1, 10)
(2, 20)
(3, 30)
(4, 40)
(5, 50)
(6, 60)
(7, 70)
(8, 80)
(9, 90)
Populated mapped segment in hex:
00 00 00 00 00 00 00 00  01 00 00 00 0a 00 00 00
02 00 00 00 14 00 00 00  03 00 00 00 1e 00 00 00
04 00 00 00 28 00 00 00  05 00 00 00 32 00 00 00
06 00 00 00 3c 00 00 00  07 00 00 00 46 00 00 00
08 00 00 00 50 00 00 00  09 00 00 00 5a 00 00 00
Contents of point-array.data:
(0, 0)
(1, 10)
(2, 20)
(3, 30)
(4, 40)
(5, 50)
(6, 60)
(7, 70)
(8, 80)
(9, 90)

UNIXコマンドライン・ツールhexdumpなど、別のアプリケーションでpoint-array.dataの内容を確認できます。

$ hexdump -C point-array.data
00000000  00 00 00 00 00 00 00 00  01 00 00 00 0a 00 00 00  |................|
00000010  02 00 00 00 14 00 00 00  03 00 00 00 1e 00 00 00  |................|
00000020  04 00 00 00 28 00 00 00  05 00 00 00 32 00 00 00  |....(.......2...|
00000030  06 00 00 00 3c 00 00 00  07 00 00 00 46 00 00 00  |....<.......F...|
00000040  08 00 00 00 50 00 00 00  09 00 00 00 5a 00 00 00  |....P.......Z...|
00000050

次の強調表示された文では、読取りおよび書込み用に開かれるFileChannelが作成されます。

try (var fc = FileChannel.open(Path.of("point-array.data"),
              Set.of(CREATE, READ, WRITE));
     Arena arena = Arena.ofConfined()) {

この文は、次のStandardOpenOptionオプションを指定します。

  • CREATE: ファイルが存在しない場合は新しいファイルを作成します
  • READ: 読取りアクセス用にファイルを開きます
  • WRITE: 書込みアクセス用にファイルを開きます

次の文は、新しいメモリー・セグメントを作成し、それをファイル・チャネル内の領域にマップします。

MemorySegment mapped = fc.map(READ_WRITE, 0L, ptsLayout.byteSize(), arena);

この文は、FileChannel.map(FileChannel.MapMode, long, long, Arena)メソッドをコールし、次の特性を持つメモリー・セグメントを作成します。

  • ファイルpoint-array.dataの読取りと書込みの両方が可能
  • ファイルの先頭から開始する
  • ファイルと同じサイズ
  • そのライフサイクルは、以前に宣言された限定アリーナによって制御される

この例では、「メモリー・レイアウトおよび構造化アクセス」で説明されている例と同じ方法で、マップされたメモリー・セグメントに移入します。

この例では、point-array.dataの内容を読み取るために、読取り専用ファイル・チャネルを作成します。

var fc = FileChannel.open(Path.of("point-array.data"),
         Set.of(READ)

次に、point-array.dataにマップされる読取り専用メモリー・セグメントを作成します。

MemorySegment mapped = fc.map(READ_ONLY, 0L, ptsLayout.byteSize(), arena);