【lucene】MMapDirectory 在FSDirectory基础上干了啥?
一句话:
**MMapDirectory 只是把 “读写文件” 从普通的 FileChannel I/O 换成了 mmap(内存映射),其余所有逻辑(文件创建、删除、锁、pending_segments 处理等)都和 FSDirectory 共用同一套代码**。
---
### 1. FSDirectory 已经做了什么
FSDirectory 提供了:
- 创建/打开/删除文件的统一实现(createOutput / openInput / deleteFile)
- 对 pendingDeletes、文件锁、临时文件的处理
- 可插拔的 `FSIndexOutput`/`FSIndexInput` 实现(用 `FileChannel.read/write` 做真正的 I/O)
---
### 2. MMapDirectory 只改了 I/O 实现
```java
public final class MMapDirectory extends FSDirectory {
...
@Override
public IndexInput openInput(String name, IOContext context) throws IOException {
ensureOpen();
Path path = directory.resolve(name);
// 用 mmap 而不是 FileChannel.read
return new MMapIndexInput(name, path, chunkSizePower, preload, useUnmapHack);
}
/* IndexOutput 仍然是 FSIndexOutput(FileChannel.write),
因为 mmap 写文件需要 RandomAccessFile + rw,且性能不一定更好,
Lucene 只在“读”路径用 mmap,“写”路径保留 FileChannel。 */
}
```
- **读文件** → `MMapIndexInput`:把文件映射到 `MappedByteBuffer`,后续读操作就是内存访问。
- **写文件** → 仍用 `FSIndexOutput`(FileChannel.write),因为:
- mmap 写需要 `RandomAccessFile` + `MapMode.READ_WRITE`,且每次扩展文件长度要重新映射,成本高;
- 写通常是顺序大块写入,FileChannel 反而更快。
---
### 3. 额外提供的调优参数
MMapDirectory 额外暴露:
- `setPreload(true)`:在 map 后立即 `madvise(MADV_WILLNEED)`,减少首次访问缺页。
- `setUseUnmapHack(true)`:用 Cleaner 或 Unsafe 手动 `unmap`,避免 64-bit JVM 下映射残留导致 “无法删除文件” 的问题(Windows 上尤其重要)。
---
### 总结对比
| 维度 | FSDirectory 默认实现 | MMapDirectory |
|-----------------|----------------------|------------------------------------|
| Directory 框架 | ✅ 同一套 | ✅ 继承 FSDirectory |
| 读文件 | FileChannel.read | mmap(MappedByteBuffer) |
| 写文件 | FileChannel.write | 同 FSDirectory(未用 mmap) |
| 额外调优 | 无 | preload、unmapHack、chunkSizePower |
| 适用场景 | 通用 | 读多写少,64-bit、大索引、随机查 |
因此:**MMapDirectory = FSDirectory + 读路径换成 mmap,其余完全复用**。
对,**在 open reader 的环节差异最大**:
1. 当你调用
`DirectoryReader.open(directory)` 或 `IndexReader.open(...)`
最终都会走到 `SegmentCoreReaders` → `openInput(name, IOContext.READONCE / RANDOM / MERGE)` 去为每个段文件(`.cfs`, `.fdx`, `.tim`, `.doc`, …)创建 `IndexInput`。
2. 如果目录是:
- **FSDirectory** → 返回 `FSIndexInput`,底层是 `FileChannel.read`
- **MMapDirectory** → 返回 `MMapIndexInput`,底层是 `MappedByteBuffer`(mmap)
3. **差异只在读阶段**
- 写文件时两者都走 `FSIndexOutput`(FileChannel.write),没有 mmap。
- 因此只有 **reader 打开后** 的随机访问、倒排表遍历、跳表查询等读操作,才会体验到 mmap 带来的:
- 地址空间直接映射,减少一次内核→用户空间拷贝
- 大文件分段映射,降低 GC 压力
- 预读/缓存效果由 OS 页缓存自动完成
4. **对上层 API 没区别**
上层代码(`IndexSearcher.search`, `TermsEnum`, `StoredFields`…)感知不到,只是底层 `IndexInput` 实现不同。
一句话:**writer 阶段无差异;reader 阶段因 mmap 而提速,尤其随机读多的查询**。