BTreeMap
BTreeMap为MapDB 提供TreeMap 和TreeSet 。它基于无锁并发B-Linked-Tree。它为小键提供了出色的性能,并具有良好的垂直可扩展性。
参数
BTreeMap具有可以使用制造商指定的可选参数:
其中最重要的是序列化器。一般序列化有一些猜测和开销,因此如果使用更特定的序列化器,总是会实现更好的性能。要指定键值和值序列化程序,请使用下面的代码。有几十个可以在Serializer界面上使用可用作静态字段的序列化器:
BTreeMap<Long, String> map =
db.treeMap("map")
.keySerializer(Serializer.LONG)
.valueSerializer(Serializer.STRING)
.createOrOpen();
另一个有用的参数是大小计数器。默认情况下,BTreeMap不会跟踪其大小,并调用map.size()需要线性扫描来计算所有条目。如果启用大小计数器,在这种情况下,map.size()是即时的,但是在插入上有一些开销。
BTrees将其所有键和值作为btree节点的一部分存储。节点大小影响性能很多。大节点意味着许多密钥必须在查找时反序列化。较小的节点加载速度更快,但是使BTree更大,需要更多的操作。
默认最大节点大小为32个条目,可以通过以下方式更改:
BTreeMap<Long, String> map =
db.treeMap("map", Serializer.LONG, Serializer.STRING)
.counterEnable()
.createOrOpen();
值也存储为BTree叶节点的一部分。大值意味着巨大的开销,并且在单个map.get(“key”)上,32个值被反序列化,但只返回一个值。在这种情况下,最好将叶节点外的值存储在单独的记录中。在这个
情况下,叶节点只有一个6字节的recid指向该值。
也可以压缩大值以节省空间。此示例将值存储在BTree Leaf Node外部,并对每个值应用压缩:
BTreeMap<Long, String> map =
db.treeMap("map")
.valuesOutsideNodesEnable()
.valueSerializer(new SerializerCompressionWrapper(Serializer.STRING))
.createOrOpen();
BTreeMap需要以某种方式对其进行排序。默认情况下,它依赖于大多数Java类实现的Comparable 接口。如果此接口未实现,则必须提供主键串行器。可以比较对象数组:
BTreeMap<Object[], Long> map = db.treeMap("map")
// use array serializer for unknown objects
.keySerializer(new SerializerArray())
// or use wrapped serializer for specific objects such as String
.keySerializer(new SerializerArray(Serializer.STRING))
.createOrOpen();
Also primitive arrays can be used as keys. One can replace String by byte[] , which directly leads to better performance:
还可以使用原始数组作为键值。 使用byte[]来替换String将获得更好的性能:
BTreeMap<byte[], Long> map = db.treeMap("map")
.keySerializer(Serializer.BYTE_ARRAY)
.valueSerializer(Serializer.LONG)
.createOrOpen();
主键序列化
BTreeMap处理键值有着自己的性能优势。 我们拿Long键值作为例子来说明这一点。
一个long型键值序列化后占用8字节。 为了最小化空间使用量,键值可以进行压缩。 值10将占用一个字节,300将占用2个字节,10000三字节等。为了使键值压缩得更小,我们需要将它们存储在更小的值中。 键被排序,所以让我们使用增量压缩。 这将以完整形式存储第一个值,然后仅存储连续数字之间的差异。
另一个改进是使反序列化更快。 在正常的TreeMap中,键值以封装形式存储,如Long []。 这有一个巨大的开销,因为每个键需要一个新的指针,类头… BTreeMap将存储键在原始数组long []中。 和
最后如果键足够小,甚至可以适应int []。 而且由于阵列具有更好的内存位置,因此二进制搜索的性能提升很大。
对数字进行这样的优化很简单。 但是BTreeMap也适用于其他键,例如String(通用前缀压缩,带偏移量的byte[]),byte [],
UUID,日期等。
这种优化是被自动使用。 你所要做的是提供专用的主键序列化程序:.keySerializer(Serializer.LONG)。
开发包里已经提供了几种现成的实现,Serializer类中的以_PACK做后缀。
碎片
无锁设计的代价是删除后产生的碎片。 B-Linked-Tree被置空时,他不会直接删除所有的btree结点,而是当所有的记录删除后,才进行删除。如果您填写了BTreeMap,然后删除所有条目,大约40%的空间将不会被释放。 任何值更新(保留的主键)都不受此碎片的影响。
这个碎片与存储上的碎片不同,所以DB.compact()将不会有起作用。 解决方案是将所有内容移动到新的BTreeMap中。 由于Data Pump流式传输速度非常快,因此新的Map将具有零碎片和更好的节点位置(理论上磁盘缓存友好)。
在将来,MapDB将提供BTreeMap封装,这将自动执行此压缩。 它将使用三个集合:第一个BTreeMap将是只读的,并且还将包含数据。 第二个小map将包含更新。 定期地,第三张地图将作为前两个的合并生成,并将与主要的交换。 这与Cassandra等数据库中的SSTable工作原理相同。
对于基于数组的键(元组,字符串或数组),MapDB提供前缀子映射。 它使用间隔,所以前缀子地图是懒惰的,它不加载所有的键。 这里作为使用byte []键前缀的例子:
BTreeMap<byte[], Integer> map = db
.treeMap("towns", Serializer.BYTE_ARRAY, Serializer.INTEGER)
.createOrOpen();
map.put("New York".getBytes(), 1);
map.put("New Jersey".getBytes(), 2);
map.put("Boston".getBytes(), 3);
//get all New* cities
Map<byte[], Integer> newCities = map.prefixSubMap("New".getBytes());
复合主键和元组
MapDB允许以Object []的形式使用复合键。 间隔子图可用于获取元组子组件,或创建简单形式的多重映射。 对象数组不可比较,所以你需要使用可以提供比较器的专用串行器。
这是一个以Object []的形式创建Map < Tuple3 < String,String,Integer>,Double>的示例。 第一部分是城市,二是街道和第三部分是房屋号码。
它有更多的部分,源代码在github上串行化和比较元组
使用SerializerArrayTuple,它将每个元组组件的序列化作为s构造函数参数:
// initialize db and map
DB db = DBMaker.memoryDB().make();
BTreeMap<Object[], Integer> map = db.treeMap("towns")
.keySerializer(new SerializerArrayTuple(
Serializer.STRING, Serializer.STRING, Serializer.INTEGER))
.valueSerializer(Serializer.INTEGER)
.createOrOpen();
一旦地图填满,我们就可以使用前缀submap(镇是元组中的第一个组件)获得Cong镇的所有房屋:
//get all houses in Cong (town is primary component in tuple)
Map<Object[], Integer> cong =
map.prefixSubMap(new Object[]{"Cong"});
前缀子映射等于使用submap方法的范围查询:
间隔子图只能过滤左侧的组件。 要在中间获取组件,我们必须将子映射与forloop组合:
cong = map.subMap(
new Object[]{"Cong"},
//shorter array is 'negative infinity'
new Object[]{"Cong",null,null} // null is positive infinity'
);
子图是可修改的,所以我们可以通过在子地图等上调用clear()来删除一个城市内的所有房屋。
Multimap是将多个值与单个键相关联的地图。 在Guava或Eclipse Collections中可以找到一个例子可以写成Map < Key,List >,但是在MapDB中不能正常工作,我们需要键和值是不可变的,List不是不可变的。
有计划在MapDB中直接从Guava和EC执行Multimap。 但是直到那时,可以使用SortedSet与元组和间隔子集的组合。 这是一个构造Set,插入一些数据并获取与key(第一个元组组件)关联的所有值(第二个元组))的示例:
// initialize multimap:
Map<String,List<Integer>> NavigableSet<Object[]> multimap =
db.treeSet("towns")
//set tuple serializer
.serializer(new SerializerArrayTuple(Serializer.STRING, Serializer.INTEGER))
.counterEnable()
.createOrOpen();
// populate, key is first component in tuple (array), value is second
multimap.add(new Object[]{"John",1});
multimap.add(new Object[]{"John",2});
multimap.add(new Object[]{"Anna",1});
// print all values associated with John:
Set johnSubset = multimap.subSet(
new Object[]{"John"}, // lower interval bound
new Object[]{"John", null}); // upper interval bound, null is positive infini
BTreeMap更适用于较小的键,例如数字和短字符串。