cmu15445 2019 Project #2 hash table
Hash Table
page表其实也是⼀个hash table,我们通过传⼊⼀个page id,能找到对应的frame,或者是传⼊一个page id,能找到磁盘上所对应的位置。
hash table是⼀种由两部分组成的数据结构:hash函数和hashing scheme。
hash函数
hash函数就是拿到⼀个任意的key,然后计算出⼀个值返回给我们。
hashing scheme
hashing scheme是当hash table中遇上hash碰撞时,⽤来处理这种问题的⼀种机制,与所使⽤的hash函数并没有什么关系,hash函数可以是最慢的那个,也可以是最快的那个,hashing scheme的⼯作⽅式其实都是⼀样的 ,都是在我们做完hash计算,跳转到某个位置时才做的。
两类hashing scheme:和dynamic hashing
static hashing scheme:当我们分配内存时,我们⼀开始就得知道我们希望保存的key的数量。因此当hash表满了以后,需要进行扩容且重建(将原来的hash-value对全部复制过去),一般是扩容到原来的2倍。
dynamic hashing:可以在不重建的前提下对hash表进行扩容。例如:Chained Hashing(Java的HashMap便采用的这种方式)。
局限
hash函数又称散列函数,值相近的两个值可能hash值相差很远,被放在相隔很远的两个桶里,从而不能保证顺序性,不支持范围查找。
TASK #2 - HASH TABLE IMPLEMENTATION
hash_table_block_page
class HashTableBlockPage {
private:
std::atomic_char occupied_[(BLOCK_ARRAY_SIZE - 1) / 8 + 1];
// 0 if tombstone/brand new (never occupied), 1 otherwise.
std::atomic_char readable_[(BLOCK_ARRAY_SIZE - 1) / 8 + 1];
MappingType array_[0];
};
block page中用的是一个个比特存储page中的每个slot位置的情况。每一个occupied和readable元素都是8bit,可存储8个slot的存储情况。所以在获得某个slot在block_page中的slot_offset_t下标时,需要先除以8,再模8,计算出在第几个字节的第几个比特,然后对这个比特进行修改。
LinearProbeHashTable
构造函数
构造函数分为两部分,初始化header page和block page
1)初始化header page,自然是初始化header page的各个成员变量
class HashTableHeaderPage {
private:
__attribute__((unused)) lsn_t lsn_; //Log sequence number (Used in Project 4) 暂且先不用管
__attribute__((unused)) size_t size_;
__attribute__((unused)) page_id_t page_id_;
__attribute__((unused)) size_t next_ind_ = 0;
__attribute__((unused)) page_id_t block_page_ids_[0];
}
需要注意的是,调用buffer_pool_manager_->NewPage(&header_page_id_);
返回的page分为header部分和data部分,而我们hash table中header page的各个成员变量属于数据,应放在data部分中,也就是说,data部分才应该是我们的header page。
2)初始化block page,同样使用BufferPoolManager来获取,有可能获取失败,需要重新获取。获取成功后,添加到header page的block_page_ids_数组中。
无论是header page,还是block page,都需要调用一次buffer_pool_manager_->UnpinPage(page_id,is_dirty);
,因为获取出来的是时候,默认其pin_count=1,所以要unpin一下,区别是header page是脏页,block page不是脏页,所以is_dirty参数不一样。
GetIndex()
获取key所对应的哈希表中的下标
哈希表存储数据的部分,由block page串联而成,而block page中又可以存放数个元素。
调用哈希函数hash_fn_.GetHash(key),获得key所对应的hash值。
bool HASH_TABLE_TYPE::GetValue
根据key获取value
因为hash table是key non-unique的,所以在搜索的过程中不能在找到第一个item后马上返回。考虑这样一种情况,在key a对应的第一个item和最后一个item之间存在key b的item,现在删除掉key b的item。此时如果slot只有readable和free两种状态,将无法处理GetValue函数。tombstone就是使用occupied来将这种被删除的slot标记出来,使得遍历到这里的时候不会停止,从而找到所有符合要求的slot。
(1)获取header page。
(2)调用GetIndex()获取元素的各个层级的下标,然后根据header page和bucket_ind获取block page。然后从block page的slot_ind位置开始遍历寻找对应的value值。只要不遇到空的slot位置,就一直向下寻找,将每一个key相同的value加入到result数组中。或者当再次遍历到开始的起点时,结束循环。
(3)要将调用的header_page和block_page通过buffer_pool_manager
unpin一下。将所有获得的锁释放掉。
bool HASH_TABLE_TYPE::Insert
(1)调用GetIndex()获取index,block_ind和slot_ind。
(2)获得header_page和block_ind对应的block_page
(3)然后从block_page的slot_ind位置开始循环,直到block_page->IsReadable(slot_ind)为false时,将key-value键值对插入到block_page的array_的slot_ind位置,退出循环,返回true。若遍历到block_page的最后一个slot,则slot_ind归零,block_ind++(如果递增之后越界,则归零),获取下一个block_page。若block_ind * BLOCK_ARRAY_SIZE + slot_ind为index,则已经完成所有block_page的遍历,返回false。
(4)返回之前需要将block_page和header_page都unpin一下,释放所有的锁。
bool HASH_TABLE_TYPE::Remove
因可能存在多个key相同value不相同的键值对,因此remove一个键值对需要key和value都检查。
(1)调用GetIndex()获取index,block_ind,slot_ind。
(2)获取hedaer_page和block_ind对应的block_page。
(3)然后从block_page的slot_ind位置开始遍历,当block_page->IsReadable(slot_ind)为false时,退出循环,返回false。判断comparator_(block_page->KeyAt(slot_ind), key) == 0 && block_page->ValueAt(bucket_ind) == value
是否为真,如果是的话,则调用block_page->remove,结束循环,返回true。同样,如果slot_ind和block_ind在遍历过程中到达临界,则进行归零操作。如果已经完成所有block_page的遍历,则退出循环,返回false。
(4)返回之前需要将block_page和header_page都unpin一下,释放所有的锁。
void HASH_TABLE_TYPE::Resize(size_t initial_size)
(1)计算扩容之后的block_page的数量,num_buckets = initial_size * 2 / BLOCK_ARRAY_SIZE。
(2)将原来的header_page_id存储为del_header_page_id,使用buffer_pool_manager_->NewPage(&header_page_id)重新申请一个header_page_all,然后初始化header_page_all里面的header_page。
(3)然后重复构造函数的操作,申请block_page,并将page_id记录在header_page中。
(4)然后将del_block_page中的key-value对全部移动到新的blcok_page中,通过block_page->Insert()来对原来的key-value对进行rehash操作。
(5)不管是原来的page,还是新的page,都需要记得unpin操作和释放锁操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通