Backing a Memory Segment with a Memory Region Inside a File
A memory region inside a file can back a memory segment. To do this, you map a region of a file channel into a memory segment with the FileChannel::map method.
The following example is similar to the one described in Memory Layouts and Structured Access. It creates a memory segment and then populates it with a native array of 10
Point
structures. The difference is that the memory segment is
mapped to a region in a file channel. The example creates a file named
point-array.data
, and then populates it. It then opens
point-array.data
with a read-only file channel and uses the FFM
API to retrieve its contents.
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;
}
}
Tip:
It is recommended that you declare VarHandles asstatic
final
for performance reasons.
It prints the following output:
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)
You can verify the contents of point-array.data
with another
application, such as the UNIX command-line tool hexdump
:
$ 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
The following highlighted statement creates a FileChannel that's open for reading and writing:
try (var fc = FileChannel.open(Path.of("point-array.data"),
Set.of(CREATE, READ, WRITE));
Arena arena = Arena.ofConfined()) {
The statement specifies the following StandardOpenOption options:
- CREATE: Creates a new file if it does not exist
- READ: Opens the file for read access
- WRITE: Opens the file for write access
The following statement creates a new memory segment and maps it a region in a file channel:
MemorySegment mapped = fc.map(READ_WRITE, 0L, ptsLayout.byteSize(), arena);
The statement calls the method FileChannel.map(FileChannel.MapMode, long, long, Arena) and creates a memory segment with the following characteristics:
- Can both read from and write to the file
point-array.data
- Starts at the beginning of the file
- Has the same size as the file
- Its lifecycle is controlled by the previously declared confined arena
The example populates the mapped memory segment the same way as the example described in Memory Layouts and Structured Access.
To read the contents of point-array.data
, the example creates a
read-only file channel:
var fc = FileChannel.open(Path.of("point-array.data"),
Set.of(READ)
The example then creates a read-only memory segment that's mapped to
point-array.data
:
MemorySegment mapped = fc.map(READ_ONLY, 0L, ptsLayout.byteSize(), arena);