Memory-Mapped Files in Java
A memory-mapped file is a file that the operating system makes appear directly in the program's memory. Instead of calling read and write, you get a MappedByteBuffer and read or change bytes in it like an array; the operating system loads pages of the file when they're touched and writes changes back by itself. You create one with FileChannel.map. Memory mapping is very fast for large files and random access, and it lets two programs share the same data. In this lesson, you will learn how to map a file and when it's worth it.
How to Map a File
try (FileChannel ch = FileChannel.open(path, READ, WRITE)) {
MappedByteBuffer map = ch.map(FileChannel.MapMode.READ_WRITE, 0, size);
map.put(index, value); // writes go straight to the mapped file
}| Map mode | Meaning |
|---|---|
READ_ONLY | It is used to read the file; writing throws. |
READ_WRITE | Changes are written back to the file. |
PRIVATE | Changes stay in memory and never reach the file (copy-on-write). |
Example: Updating a File Through Memory
Let's see an example that maps a counter file and changes numbers in place.
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class Mapped {
public static void main(String[] args) throws IOException {
Path file = Path.of("counters.dat");
int counters = 4;
try (FileChannel ch = FileChannel.open(file,
StandardOpenOption.CREATE, StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
MappedByteBuffer map = ch.map(
FileChannel.MapMode.READ_WRITE, 0, counters * 4L);
for (int round = 0; round < 1000; round++) {
int slot = round % counters;
map.putInt(slot * 4, map.getInt(slot * 4) + 1);
}
map.force();
System.out.println("File size: " + ch.size() + " bytes");
}
try (FileChannel ch = FileChannel.open(file)) {
MappedByteBuffer map = ch.map(FileChannel.MapMode.READ_ONLY,
0, ch.size());
StringBuilder values = new StringBuilder();
for (int i = 0; i < counters; i++) {
values.append(map.getInt(i * 4)).append(' ');
}
System.out.println("Counters: " + values.toString().strip());
}
}
}File size: 16 bytes
Counters: 250 250 250 250Explanation: Mapping 16 bytes in READ_WRITE mode also grew the new file to 16 bytes. getInt(i) and putInt(i, v) read and write directly at byte offsets, with no read or write calls. force() asks the operating system to save the changes to disk. Reopening the file with a read-only map shows the four counters, each increased 250 times.
When to Use Memory Mapping
| Good fit | Poor fit |
|---|---|
| Very large files read in random order | Small files read once from start to end |
| Databases, search indexes, caches | Files on network drives |
| Sharing data between processes | Files whose size changes often |
Common Mistakes
Mapping more than 2 GB in one MappedByteBuffer. Its index is an int; map large files in several regions, or use MemorySegment.
Writing to a READ_ONLY map. It throws ReadOnlyBufferException.
Assuming changes are on disk immediately. Call force() when durability matters.
Practice
- Easy: Map a text file read-only and count how many bytes are the letter
a. - Harder: Create a 100 MB file, then time summing every 4096th byte with a mapped buffer and with a
RandomAccessFileusingseek.
Key Takeaways
Was this tutorial helpful?