MapDB 教程三

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更适用于较小的键,例如数字和短字符串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值