内存映射文件
你有没有遇到过这个问题:
一个 10GB 的日志文件,你只想修改其中的第 5GB 那一行。用普通 FileInputStream 需要怎么读?
答案是:先读 5GB,跳过它,才能到那一行。
有没有更好的方式?内存映射文件就是答案。
为什么大文件随机访问慢
传统的 read/write 需要:
- 从磁盘读数据到内核缓冲区
- 从内核缓冲区拷贝到用户空间
- 每次读写都要经过这个流程
如果文件很大但只需要访问一小部分,传统 IO 会有大量无效的数据传输。
内存映射:像操作数组一样操作文件
FileChannel.map() 把文件直接映射到内存,程序像操作内存一样操作文件:
java
public static void processMappedFile(String path) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(path, "rw");
FileChannel channel = raf.getChannel()) {
long fileSize = channel.size();
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, fileSize);
// 像操作内存数组一样操作文件
while (buffer.hasRemaining()) {
buffer.put(buffer.get());
}
}
}映射后,buffer.get() 和 buffer.put() 直接操作磁盘文件,没有任何系统调用——只有第一次访问时触发 page fault,把数据加载到内存。
MapMode:三种映射模式
| 模式 | 说明 |
|---|---|
READ_ONLY | 只读映射,更改不生效 |
READ_WRITE | 读写映射,更改自动同步到磁盘 |
PRIVATE | 私有映射,更改不写回磁盘(写时复制) |
java
// 只读映射
MappedByteBuffer readOnly = channel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
// 读写映射
MappedByteBuffer readWrite = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
// 私有映射(写时复制)
MappedByteBuffer privateMap = channel.map(FileChannel.MapMode.PRIVATE, 0, fileSize);适合场景
- 大文件随机读写:比如修改日志文件的某一行
- 高性能数据处理:数据库索引文件、游戏资源文件
- 需要反复访问同一块数据:避免重复 IO
超大文件:分段映射
内存映射受 OS 页面大小限制(通常 4KB),单个映射不能超过 2GB。
对于超大文件,需要分段映射:
java
public static void processLargeFile(String path) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(path, "rw");
FileChannel channel = raf.getChannel()) {
long fileSize = channel.size();
long position = 0;
long chunkSize = 1024L * 1024 * 1024; // 1GB 每段
while (position < fileSize) {
long remaining = fileSize - position;
long size = Math.min(remaining, chunkSize);
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, position, size);
// 处理 buffer
while (buffer.hasRemaining()) {
buffer.put(buffer.get());
}
position += size;
}
}
}性能对比
| 方式 | 适用场景 | 性能 |
|---|---|---|
| 普通 read/write | 顺序读写 | 好 |
| Buffered 读写 | 频繁小读写 | 更好 |
| 内存映射 | 大文件随机读写 | 最好 |
注意事项
- 映射大小受 OS 限制:单个映射通常不超过 2GB
- 更改自动同步:READ_WRITE 模式的修改会自动写回磁盘
- 关闭 Channel 后映射失效
- 内存占用高:映射的大小直接影响物理内存占用
┌─────────────────────────────────────────────────────────────────┐
│ 内存映射的本质:把文件的一部分「变成」内存 │
│ │
│ 优点:随机访问极快,无传统 IO 的系统调用开销 │
│ 缺点:占用物理内存,不适合小型文件 │
│ │
│ 适用:大文件随机访问 + 高性能场景 │
└─────────────────────────────────────────────────────────────────┘