Extendible Hash Table 由一个 directory 和多个 bucket 组成。
首先让我们来了解如何使用可拓展哈希表进行查找操作。可拓展哈希表示意图如上图所示。注意图中的"global"和"local"代指全局深度和局部深度。当需要查询A时,计算A的哈希值为"01110…"。由于此时全局深度为2,因而取前两位"01"作为key在目录中进行查找(当然,也可以取后两位)。注意到目录中的"00"和"01"共用同一个桶,这是因为它们指向的桶的局部深度为1,而"00"和"01"的第一位均为0。对B的查询同理,不过注意"10"和"11"对应桶的局部深度为2,因而两者指向不同的桶。
当需要插入值是,首先看看其对应的桶是否已满,如果未满则直接插入;如果已满,则需要执行裂桶(split)操作。
如果需要分裂的桶的局部深度小于全局深度,则将局部深度+1,分裂为两个桶,之后将其中存储的值,包括新插入的值,分配到对应的桶中。
如果需要分裂的桶的局部深度等于全局深度,如下图例子所示,那么则需要增加全局深度后再进行裂桶操作。
该任务不要求处理收缩(shrink),因而删除则直接找到对应值后删除即可。
当缓存池需要空闲帧,但所有帧均已满时,则需要从数组中驱逐出某个页,来为新的页腾出空间。选择哪个页进行驱逐,取决于缓存替换策略(Buffer Replacement Policies)。最知名的缓存替换策略为LRU(Least Recently Used)算法,其每一次需要驱逐时,驱逐最久未被使用的页。这一策略依据的原理为时间局部性:在一个具有良好时间局部性的程序中,被引用一次的页很可能在不久的将来被再次引用。
然而,LRU算法不能很好的处理sequential flooding问题。当数据库在对长序列进行扫描时,如果采用LRU算法,则缓存中的原有内容会被序列中内容代替。但问题在于,序列很可能只被访问一次,即在扫描序列中,最近被访问的内容往往短时间内不会被再次访问。
这一问题的解决方法之一是使用LRU算法,即以页最后第K次被访问的时间戳作为驱逐的依据。驱逐时,驱逐时间戳最小的页。
在完成上面两个任务之后,就可以开始实现缓存池了。总体来说该实验并不难,按照文档的要求进行即可。
项目一在四个项目中可以算是最简单的一个。按照文档要求捋清楚思路,事先写好伪代码,并结合测试样例调试,很容易就能通过。另外,该项目还设计到了读写锁的操作,但相比于P2而言,这里对并行的考察并不十分严格,想要省事的话可以直接一把大锁锁住公共的数据结构。
附gradescore提交的结果: