1. 简介
本文介绍 MapDB —— 一个以内存友好的方式提供嵌入式数据库功能的 Java 库,其接口风格与 Java 集合类似。
我们会从核心类 DB
和 DBMaker
开始,讲解如何配置、打开和管理数据库。接着会通过几个数据结构示例,展示 MapDB 如何存储和检索数据。
最后,我们会比较 MapDB 与传统数据库、Java 集合之间的差异,并说明其适用场景。
2. 数据存储基础
MapDB 的核心类有两个:
DB
:表示一个打开的数据库实例,用于创建和管理集合(如 Map、Set)、处理事务等。DBMaker
:用于配置数据库的创建方式,可以选择内存或文件方式打开数据库。
2.1. 一个简单的 HashMap 示例
我们先创建一个内存数据库:
DB db = DBMaker.memoryDB().make();
然后创建一个 HTreeMap
,它是 MapDB 提供的 Map 实现:
String welcomeMessageKey = "Welcome Message";
String welcomeMessageString = "Hello Baeldung!";
HTreeMap myMap = db.hashMap("myMap").createOrOpen();
myMap.put(welcomeMessageKey, welcomeMessageString);
获取数据也很简单:
String welcomeMessageFromDB = (String) myMap.get(welcomeMessageKey);
assertEquals(welcomeMessageString, welcomeMessageFromDB);
使用完后记得关闭数据库:
db.close();
如果想将数据保存到文件中,只需改用:
DB db = DBMaker.fileDB("file.db").make();
上面的例子没有使用泛型,因此需要强制类型转换。接下来我们引入序列化器(Serializer)来解决这个问题。
2.2. 集合操作示例
MapDB 支持多种集合类型,比如 NavigableSet
:
DB db = DBMaker.memoryDB().make();
NavigableSet<String> set = db
.treeSet("mySet")
.serializer(Serializer.STRING)
.createOrOpen();
set.add("Baeldung");
set.add("is awesome");
assertEquals(2, set.size());
set.add("Baeldung"); // 重复添加
assertEquals(2, set.size());
2.3. 事务支持
MapDB 支持事务,使用方式类似传统数据库:
DB db = DBMaker.memoryDB().transactionEnable().make();
NavigableSet<String> set = db
.treeSet("mySet")
.serializer(Serializer.STRING)
.createOrOpen();
set.add("One");
set.add("Two");
db.commit();
assertEquals(2, set.size());
set.add("Three"); // 未提交
assertEquals(3, set.size());
db.rollback(); // 回滚
assertEquals(2, set.size());
2.4. 序列化器(Serializer)
MapDB 提供多种内置序列化器,可以避免强制类型转换,提升性能:
HTreeMap<String, Long> map = db.hashMap("myMap")
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.LONG)
.create();
虽然可以不指定序列化器,但会导致使用通用的慢速序列化机制。
3. HTreeMap
HTreeMap
是 MapDB 中的 HashMap 实现,具有以下特点:
✅ 线程安全
✅ 支持并发写入
✅ 不使用固定大小哈希表,而是使用自动扩展的索引树
示例代码如下:
DB db = DBMaker.memoryDB().make();
HTreeMap<String, String> hTreeMap = db.hashMap("myTreeMap")
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.STRING)
.create();
hTreeMap.put("key1", "value1");
hTreeMap.put("key2", "value2");
assertEquals(2, hTreeMap.size());
重复 key 会覆盖值:
hTreeMap.put("key1", "value3");
assertEquals("value3", hTreeMap.get("key1"));
4. SortedTableMap
SortedTableMap
是一种只读的有序 Map,适用于数据准备完成后不再修改的场景。它使用二分查找进行高效检索。
创建并读取数据
String VOLUME_LOCATION = "sortedTableMapVol.db";
Volume vol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, false);
SortedTableMap.Sink<Integer, String> sink = SortedTableMap.create(
vol,
Serializer.INTEGER,
Serializer.STRING
).createFromSink();
for (int i = 0; i < 100; i++) {
sink.put(i, "Value " + i);
}
sink.create();
读取时使用只读模式打开:
Volume openVol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, true);
SortedTableMap<Integer, String> sortedTableMap = SortedTableMap.open(
openVol,
Serializer.INTEGER,
Serializer.STRING
);
assertEquals(100, sortedTableMap.size());
4.1. 二分查找机制
SortedTableMap
的查找过程分为三步:
- 在内存页表中查找目标页(基于页的 key 范围)
- 解压页内 key,查找目标 key 所在节点
- 在节点内部查找具体 key 对应的 value
这保证了即使数据量大,也能保持较高的查找效率。
5. 内存模式
MapDB 支持三种内存模式:
5.1. Heap(堆内)
- 数据直接存储在 Java 堆中
- 不使用序列化,适用于小数据集
- 缺点是受 GC 影响,数据量大会导致性能下降
DB db = DBMaker.heapDB().make();
5.2. Byte[](字节数组)
- 数据被序列化后存储在 byte 数组中
- 默认模式,适用于大多数场景
- 比堆内模式更节省 GC 开销
DB db = DBMaker.memoryDB().make(); // 默认使用 byte[]
5.3. DirectByteBuffer(堆外)
- 使用 Java NIO 的
DirectByteBuffer
,数据存储在堆外内存 - 不受 GC 控制,适合大数据量场景
- 性能最好,但需要手动管理内存
DB db = DBMaker.memoryDirectDB().make();
6. 为什么选择 MapDB?
6.1. MapDB vs 传统数据库
✅ 无需配置数据库服务
✅ 无需 SQL 语句
✅ 接口风格与 Java 集合一致,开发效率高
❌ 不支持复杂查询、连接操作等高级功能
6.2. MapDB vs Java 集合
✅ 数据可持久化,重启后不丢失
✅ 支持事务、并发、快照等特性
✅ 可以内存或文件方式运行
❌ 比原生集合稍有性能损耗
7. 总结
MapDB 是一个轻量级、嵌入式的 Java 数据库库,适合以下场景:
- ✅ 数据量中等,需要持久化
- ✅ 不想引入复杂数据库系统
- ✅ 需要线程安全、事务支持
- ✅ 希望使用 Java 集合风格操作数据
MapDB 的优势在于其简单、快速、易集成,是 Java 应用中轻量级持久化需求的理想选择。
完整示例代码可在 GitHub 获取。