CheckPoint 1
Task1:B+ Tree pages
第一个Task需要完成三个page,分别是B+Tree Page,B+ Tree Internal Page,B+Tree Leaf Page。
B+ Tree Page
这个类是InternalPage与LeafPage的基类,主要是完成一些非常简单的Get/Set方法。
最后需要注意一点,GetMinSize()这个方法,中间节点的GetMinSize()是上取整的,而叶子节点的是下取整的。比如说MaxSize=3,那么中间节点的半满被认为是2,而叶子节点的半满被认为是1。
B+ Tree Internal Page
这个类也有一些简单的Get/Set方法,这些略过。
由于存储key-value pair的数组是这个类的私有成员,将来我们在B+树中使用中间节点时,无法访问到里面的数据,所以需要视情况自己增加一些想完成某些操作的函数在里面,因为从B+树那边是无法直接修改页面里的数据的。
中间节点的第一个key总是设置为空,这是一个坑点需要注意。因为中间节点的key数量比value数量要少一个。
B+ Tree Leaf Page
叶子节点的key-value数量一致,所以第一个key不用设置为空。需要注意的点类似于中间节点,叶子节点的value是指向tuple的指针,而中间节点的value是指向叶节点的指针。
Task2:B+ Tree Insert/GetValue
B+树的插入,首先我们需要从根节点,一路前进到对应的叶节点。中间路径上的节点需要保存下来的,方便我们找到父节点,保存可以使用project里自带的Context类,里面有两个双端队列可以用来保存路径上的节点。
一旦到达指定的根节点,就要检测是否存在重复的key,不存在才可以进行插入。
然后检查该节点是否还能容纳这个key,如果能,直接插入就结束了。如果不能,那么需要先构造一个较大的临时数组,把所有的key-value放进来,在加上需要插入的那个key-value,然后进行排序。排序完之后一分为二,左半部分的给原先的叶节点,右半部分的给新建的叶节点。之后设置好兄弟指针,然后将中间节点插入到父节点。(中间节点指向新叶节点,递归插入父节点,因为父节点也可能引起分裂)
至于查询,前半部分和插入一样,到达叶节点后,直接检查存不存在对应key即可,存在的话直接返回,不涉及任何修改操作,较为简单。
CheckPoint 2
Task1:B+ Tree Delete
B+树的删除非常复杂,当节点由于被删除了某个key导致低于半满状态时,需要进行redistribute
或者merge
。
前者有四种情况,向左边兄弟节点借,向右边兄弟节点借。这两种在叶节点和中间节点发生时又各不同,因此\(2\times2=4\)种。
后者只有两种情况,向左边兄弟合并,向右边兄弟合并。
具体的步骤看这个ppt,http://courses.cms.caltech.edu/cs122/lectures-wi2018/CS122Lec11.pdf
。
Task2: Iterator
实现一个迭代器,非常容易就不多说了。
Task3: Concurrency Control
我们的插入和删除和查询都必须是线程安全的。
查询不用说,由于不涉及修改,至于给路径上的节点都挂上读锁即可。
插入和删除都需要使用到Latch Crabbing的技巧,也就是螃蟹法则。在出发前往到叶节点的过程中,需要锁上路径上所有的祖先节点,但是一旦发现当前节点是safe的,那么就把先前保存的所有祖先节点全部解锁。一个节点是safe,当且仅当对它进行插入/删除不会引起超过全满/低于半满。