MappedByteBuffer 通过 FileChannel.map() 将文件映射到堆外内存实现高效 I/O;需用 RandomAccessFile 或支持 channel 的流打开,支持 READ_ONLY 和 READ_WRITE 模式,PRIVATE 不可用。

Java 中使用 MappedByteBuffer 实现内存映射文件,核心是通过 FileChannel.map() 将文件直接映射到 JVM 的堆外内存(即操作系统虚拟内存),绕过传统 I/O 的内核态/用户态拷贝,从而大幅提升大文件读写性能。但要注意它不是“把文件全加载进堆内存”,而是按需分页映射,且需合理管理资源和并发安全。
如何创建 MappedByteBuffer
必须基于 FileChannel,且该 channel 需由支持读写(或只读)的 RandomAccessFile 或 FileInputStream/FileOutputStream(注意:后者需用 getChannel() 且仅限只读映射)打开:
- 只读映射用
FileChannel.MapMode.READ_ONLY - 读写映射用
FileChannel.MapMode.READ_WRITE(修改会同步回磁盘,但不保证立即刷盘) - 私有映射(写时复制)用
FileChannel.MapMode.PRIVATE(Java 不支持,仅保留接口,实际抛UnsupportedOperationException)
示例:
// 创建一个 1GB 映射区域(注意:文件本身需存在且足够大,或先扩容)
RandomAccessFile raf = new RandomAccessFile("data.bin", "rw");
FileChannel channel = raf.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1L << 30); // 1GB
高效读写的典型操作方式
MappedByteBuffer 继承自 ByteBuffer,支持标准的 get()/put()、批量数组操作、绝对/相对位置访问。关键优势在于:无需额外拷贝,直接操作内存地址。
立即学习“Java免费学习笔记(深入)”;
- 写入:用
put(byte)、put(byte[])、putInt()等方法直接写入映射区域,数据会异步刷回文件(依赖 OS 页面回收机制) - 读取:用
get()、getInt()、get(byte[])等快速读取,零拷贝 - 定位:调用
position(int)移动游标,或用绝对索引如get(1024)直接访问偏移量 - 刷新:若需强制落盘,调用
buffer.force()(对应msync()),但频繁调用反而降低性能
必须注意的关键限制与陷阱
内存映射不是万能方案,忽略这些会导致 OOM、数据丢失或行为异常:
-
单次映射不能超过 2GB(
int最大值限制),大文件需分段映射(如每 1.5GB 一段) -
映射后文件不能被外部进程截断或删除,否则可能引发
IOException或 SIGBUS(Linux/macOS) -
无法手动释放映射内存:JVM 不提供
unmap();只有当MappedByteBuffer被 GC 回收且底层Cleaner触发时才释放(JDK 14+ 提供FileChannel.map()的替代 API,但仍不推荐主动干预) -
并发写需自行加锁:
MappedByteBuffer本身非线程安全,多线程写同一区域必须同步 -
堆外内存不计入 -Xmx,但受系统虚拟内存和物理内存限制,映射过大易触发
OutOfMemoryError: Map failed
实用建议与替代思路
真正发挥 MappedByteBuffer 价值,要结合场景设计:
- 适合:日志追加写、索引文件随机读、大数据预处理(如二进制格式解析)
- 不适合:小文件(开销反超传统 I/O)、频繁增删导致文件大小动态变化、需要强一致性保障的事务型写入
- 提升稳定性:配合
try-with-resources确保FileChannel和RandomAccessFile关闭;对关键写入,可搭配force()+raf.getFD().sync() - 现代替代:JDK 14+ 可考虑
Files.write(..., StandardOpenOption.SYNC)或使用Chronicle-Bytes等第三方库封装映射逻辑


















