1. 简介

本文介绍 MapDB —— 一个以内存友好的方式提供嵌入式数据库功能的 Java 库,其接口风格与 Java 集合类似。

我们会从核心类 DBDBMaker 开始,讲解如何配置、打开和管理数据库。接着会通过几个数据结构示例,展示 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 的查找过程分为三步:

  1. 在内存页表中查找目标页(基于页的 key 范围)
  2. 解压页内 key,查找目标 key 所在节点
  3. 在节点内部查找具体 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 获取。


原始标题:Guide to MapDB | Baeldung