bucket_page
:用来存储键值对的page,利用readable_[]判断当前index是否存有数据,occupied判断当前index是否装过数据(按道理这玩意儿没啥用)。而数组就用来存储键值对。
directory_page
:其实可以看作是HashTable的信息存储地,因为需要并发控制以及保存关键信息,故直接利用一个page来存储这些信息,每次使用的时候提取该page出来就行。
这里没有同以往的 TASK文档一样,明确告知我需要实现的文件在哪里,需要从最后面的测试需要提交的文件列表里面去找,但文档出现了如下错误:
图中所示的extendible_probe_hash_table
并不存在,再查看后面的打包命令行可以发现,实际上的文件名是extendible_hash_table
1- extendible_hash_table的概念
:本身不会存储什么pointer,depth等数据,这些数据都是需要存储在能存进硬盘的Page
中的(directory_page
或者bucket_page
),可以把它看作一个工具类,其需要的每一个信息都是要从buffer_pool_manager
中请求得到的page
中获取。
2- 构造函数
:需要在这里新建一个directory_page
和对应的bucket_Page
,初始化global_depth = 1
和`local_depth = 1``(local_depth = 0也可以,等于1时需要新建两个bucket_page,0时只需一个),新建的方法我是在测试函数中学会的:
New()
还是Fetch()
都需要记得UnpinPage()
!bucket_page
不需要reinterpret_cast
,因为新建的时候没有需要设置的参数。2 ^ global_depth
就是长度directory_page_id
,不要声明任何关于hashtable的私有变量来存储其信息,因为需要满足并发,每次的信息必须从bplm中调取出来才能保证安全。auto directory_page =
reinterpret_cast<HashTableDirectoryPage *>(buffer_pool_manager->NewPage(&directory_page_id_, nullptr)->GetData());
directory_page->IncrGlobalDepth();
page_id_t bucket_page_id_1 = INVALID_PAGE_ID;
buffer_pool_manager->NewPage(&bucket_page_id_1, nullptr)->GetData();
directory_page->SetLocalDepth(0, 1);
directory_page->SetBucketPageId(0, bucket_page_id_1);
buffer_pool_manager->UnpinPage(directory_page_id_, true);
buffer_pool_manager->UnpinPage(bucket_page_id_1, true);
其他函数
:每次的逻辑大概是:根据directory_page_id_
从buffer_pool_manager
中调取directory_page
,并根据需求调取或者新建/删除bucket_page
,修改完信息后,每次都记得UnpinPage()
就行。
SplitInsert()
Insert()
,在插入之前检查是否满,满了就跳转到SplitInsert()
把插入工作全权交给它,没满就直接正常插入并返回。if (global_depth == local_depth) { // 需要增加global_depth
direct_page->global_depth++;
更新 direct_page->local_depths_[];
}
// 增加local_depth
new new_bucket_page;
old_bucket_page 元素转移-> new_bucket_page;
更新 direct_page->local_depths_[];
更新 direct_page->bucket_page_ids_[];
// 递归判断是否还需要分页
bucket_page = 现在key对应的bucket_paeg
if (bucket_page->Isfull()) {
SplitInsert();
}
Merge()
local_depth
不相等,这时候直接放弃合并。为了方便检测SplitInsert 和 Merge 是否正常工作,我给ExtendibleHashTable写了一个内置的打印函数,打印出hashtable。
template <typename KeyType, typename ValueType, typename KeyComparator>
void HASH_TABLE_TYPE::PrintTable() {
// prinf
auto directory_page = FetchDirectoryPage();
uint64_t table_size = (1 << directory_page->GetGlobalDepth());
std::cout << "-----------HASH_TABLE_MESSAGE-----------\n";
std::cout << "global_depth: " << directory_page->GetGlobalDepth() << "\n";
for (uint32_t i = 0; i < table_size; i++) {
uint32_t bucket_page_id = directory_page->GetBucketPageId(i);
uint32_t local_depth = directory_page->GetLocalDepth(i);
auto bucket_page = reinterpret_cast<HashTableBucketPage<KeyType, ValueType, KeyComparator> *>(
buffer_pool_manager_->FetchPage(bucket_page_id)->GetData());
uint32_t num_readable = bucket_page->NumReadable();
std::cout << "index: " << i << "|"
<< "bkt_id: " << bucket_page_id << "|"
<< "num_readable/size: " << num_readable << "/" << BUCKET_ARRAY_SIZE << "|"
<< "local_depth: " << local_depth << "\n";
buffer_pool_manager_->UnpinPage(bucket_page_id, false);
}
buffer_pool_manager_->UnpinPage(directory_page_id_, false);
}
官方文档用法:
auto bucket_page = reinterpret_cast<HashTableBucketPage<KeyType, ValueType, KeyComparator> *>(buffer_pool_manager_->FetchPage(old_bucket_page_id)->GetData());
auto page = reinterpret_cast<Page *> (bucket_page);
page->RLatch();
page->RUlatch();
我没有理解到的是,如果把整个bucket_page转译成另一个Page,那么使用锁的时候不会改变bucket_page
原有的数据项吗。
而且buffer_pool_manager_->FetchPage()
返回的就是一个Page,那为何不直接使用这个返回的Page自带的锁:
auto page = buffer_pool_manager_->FetchPage(old_bucket_page_id);
auto bucket_page = reinterpret_cast<HashTableBucketPage<KeyType, ValueType, KeyComparator> *> (page->GetData());
page->RLatch();
page->RUlatch();
如果要保证线程安全,应该在Unpin之后再使用Unlatch。
buffer_pool_manager_->UnpinPage(bucket_page_id, false);
page->RUnlatch();