SortedTableMap 的灵感来源于 Cassandra 中的 Sorted String Tables(有序字符串表)。它将键存储在文件(或内存存储)中的固定大小表中,并使用二分查找。有一些技巧可用于支持可变长度的条目并减少空间使用。与 BTreeMap 相比,它速度更快,零碎片,但具有只读属性。
SortedTableMap 是只读的,不支持更新。应使用数据泵(Data Pump)创建新映射来应用更改。通常,人们会将更改放入辅助映射中,并定期将这两个映射合并成新的 SortedTableMap。
SortedTableMap 是只读的。它由数据泵(Data Pump)和消费者(Consumer)创建并填充内容。
示例:
// 创建 SortedTableMap
public static class CreateSortedTableMapExample {
public static void main(String[] args) {
//创建内存映射卷
Volume volume = MappedFileVol.FACTORY.makeVolume("volume.db", false);
//打开将向映射填充内容的消费者
SortedTableMap.Sink<Integer,String> sink =
SortedTableMap.create(volume, Serializer.INTEGER, Serializer.STRING).createFromSink();
//将内容输入到消费者中
for(int key=0; key<100000; key++){
sink.put(key, "value"+key);
}
// 最后打开已创建的映射
SortedTableMap<Integer, String> map = sink.create();
// 获取映射中的值
System.out.println(map.get(50000)); // value50000
}
}文件创建后,可以重新打开:
// 重新打开 volume.db
public static class ReopenSortedTableMapExample {
public static void main(String[] args) {
//以只读模式打开现有的内存映射文件
Volume volume = MappedFileVol.FACTORY.makeVolume("volume.db", true); //read-only=true
//打开已存在的映射
SortedTableMap<Integer,String> map =
SortedTableMap.open(volume, Serializer.INTEGER, Serializer.STRING);
// 获取映射中的值
System.out.println(map.get(50000)); // value50000
}
}存储被分割成多个页。页大小是 2 的幂,最大为 1MB。每一页上的第一个键存储在堆上。
每一页都包含若干由键和值组成的节点。这些节点与 BTreeMap 的叶节点非常相似。节点的偏移量是已知的,因此可以使用快速定位到节点起始位置的方法。
每个节点包含若干个键值对(默认情况下为 32 个)。它们的组织方式取决于序列化器,但通常会被一起压缩(增量压缩、LZF……)以节省空间。因此,要找到单个条目,必须加载/遍历整个节点。一些固定长度的序列化器(Serializer.LONG……)无需加载整个节点就能找到单个条目。
对 SortedTableMap 执行二分查找分为三个步骤:
(1)每个页的第一个键存储在堆上的数组中。因此,执行二分查找来找到页。
(2)每个节点上的第一个键无需解压缩整个节点就能加载。因此,对每个节点上的第一个键执行二分查找找。
(3)现在我们知道了节点,所以要对节点的键执行二分查找。这取决于序列化器。通常需要加载整个节点,但也可能有其他选项。
SortedTableMap 采用键序列化器和值序列化器。键和值一起存储在到序列化器的数组中。它们可以压缩在一起以节省空间。序列化器在空间使用和性能之间进行权衡。
另一个设置是页面大小。默认值和最大值为 1MB。其值必须是 2 的幂,其他值会向上取整到最接近的 2 的幂。较小的值通常意味着更快的访问速度。但每个页面有一个键存储在堆上,较小的页面大小也意味着更大的内存占用。
最后是节点大小。它的含义与 BTreeMap 的节点大小类似。节点越大,压缩效果越好,因为较大的数据块更容易压缩。但这也意味着访问速度更慢,因为获取单个条目需要加载更多的条目。默认的节点大小是 32个条目,对于较大的值,应该减小节点大小。
参数按以下方式设置:
// 参数示例
public static class SortedTableMapParametersExample {
public static void main(String[] args) {
//创建内存映射卷
Volume volume = MappedFileVol.FACTORY.makeVolume("volume_params.db", false);
//打开消费者,将为 map 提供内容
SortedTableMap.Sink<Integer,String> sink =
SortedTableMap.create(volume,
Serializer.INTEGER, // 键序列化器
Serializer.STRING // 值序列化器
)
.pageSize(64*1024) // 将页面大小设置为64KB
.nodeSize(8) // 将节点大小设置为8个条目
.createFromSink();
//将内容输入到消费者中
for(int key=0; key<100000; key++){
sink.put(key, "value"+key);
}
// 最后打开创建的映射
SortedTableMap<Integer, String> map = sink.create();
// 获取映射中的值
System.out.println(map.get(50000));
// 关闭卷
volume.close();
//===========================================================
// 可以重新打开现有的 SortedTableMap。
// 在这种情况下,只需要设置序列化器,
// 其他参数存储在文件中
volume = MappedFileVol.FACTORY.makeVolume("volume_params.db", true); // read-only=true
map = SortedTableMap.open(volume, Serializer.INTEGER, Serializer.STRING);
// 再从获取
System.out.println(map.get(50000));
map.close();
}
}SortedTableMap 不使用 DB 对象,而是直接在 Volume 上操作(基于 ByteBuffer 的 MapDB 抽象)。以下示例展示了如何使用内存字节数组或内存映射文件构造各种卷:
// 基于字节数组创建内存卷
Volume byteArrayVolume = ByteArrayVol.FACTORY.makeVolume(null, false);
// 使用 DirectByteByffer 在直接内存中创建内存卷
Volume offHeapVolume = ByteBufferMemoryVol.FACTORY.makeVolume(null, false);
File file = File.createTempFile("mapdb","mapdb");
// 创建内存映射文件卷
Volume mmapVolume = MappedFileVol.FACTORY.makeVolume(file.getPath(), false);
// 或者如果数据已经导入,将其创建为只读模式
mmapVolume.close();
mmapVolume = MappedFileVol.FACTORY.makeVolume(file.getPath(), true); //read-only=true然后,卷会作为一个参数传递给 SortecTableMap 工厂方法。建议以只读模式打开已有的卷(最后一个参数为 true),以最大限度地减少文件锁定并简化代码。
数据泵会将卷内容同步到磁盘,因此一旦 Consumer.finish() 方法退出,基于文件的 SortedTableMap 就具有持久性。
更多关于 MapDB 的知识,请继续学习后续教程🚀