P1 - Buffer Pool -- Task #1 - Extendible Hash Table

文章详细介绍了ExtendibleHashing的数据结构,特别是Bucket内部类的实现,包括成员变量如size、depth和list,以及相关成员函数如Insert、Find和Remove。外部类ExtendibleHashTable的成员变量和功能,如global_depth、bucket_size和num_buckets,以及分裂桶和扩容的策略也进行了讨论。
摘要由CSDN通过智能技术生成

再熟悉一下定义 Extendible Hashing (Dynamic approach to DBMS) - GeeksforGeeks

看一下项目结构,有一个 Bucket 内部类表示桶

In addition, we provide a Bucketnested class that represents buckets in the extendible hash table. Finishing the TODOs of the Bucketclass first by following the code documentation can make it easier for you to implement the ExtendibleHashTable APIs.

根据提示先从内部类开始

​成员变量:

size_t size_;                     //桶中元素个数
int depth_;                       //桶的局部深度
std::list<std::pair<K, V>> list_; //list_ 是一个 list 类对象,里面装的是 key/value 的结构体

成员函数:

//Bucket() 构造函数传入 size 和 depth 创建一个桶,depth 默认为0
explicit Bucket(size_t size, int depth = 0);    
inline auto IsFull() const -> bool { return list_.size() == size_; }
inline auto GetDepth() const -> int { return depth_; }  
inline void IncrementDepth() { depth_++; }
inline auto GetItems() -> std::list<std::pair<K, V>> & { return list_; }

//传入 key 查找,结果赋值给引用类型参数 value,找到返回 true
auto Find(const K &key, V &value) -> bool;  
/*
使用 list 类的 .empty() .begin()/.front() .end()/.back()
front 和 back 方法返回的是元素的引用,begin 和 end 方法返回的是迭代器
使用迭代器较为方便,使用 auto 类型避免自己写复杂的返回值类型,使用 -> 即可访问元素,++ 使迭代器指向下一个元素
list 里面是 pair,要用 it->first 访问 key,记得给引用参数赋值
*/

//删除指定 key 的 k/v,删除成功返回 true
auto Remove(const K &key) -> bool;
/*
使用刚才写的 auto Find(const K &key, V &value) -> bool,list 类的 remove()
注意 list 里面类型是 pair,要使用 std::make_pair(key, value) 成对
*/

//插入 k/v,若 k 存在更新 v,返回 true,满了直接返回 false
auto Insert(const K &key, const V &value) -> bool;
/*
使用 list 类的 remove() push_back()
注意 list 里面类型是 pair,要使用 std::make_pair(key, value) 成对
*/

然后看外部类成员变量:

int global_depth_;                          // The global depth of the directory
size_t bucket_size_;                        // The size of a bucket
int num_buckets_;                           // The number of buckets in the hash table
mutable std::mutex latch_;                  // latch,用于互斥访问
std::vector<std::shared_ptr<Bucket>> dir_;  // 哈希表的目录,是一个 vector 数组,里面存储的智能指针,指向Bucket类对象

 成员函数:

ExtendibleHashTable<K, V>::ExtendibleHashTable(size_t bucket_size)
    : global_depth_(0), bucket_size_(bucket_size), num_buckets_(1) {
}
/*
构造函数提示初始全局深度为 0,桶的数量为 1
注意没有初始化目录 dir_ 需要我们手动添加实现
*/

You can make use of the provided IndexOf(K) private function to compute the the directory index that a given key hashes to

//传入 key,返回在目录中的偏移量,即 dir_[] 数组中的位置
template <typename K, typename V>
auto ExtendibleHashTable<K, V>::IndexOf(const K &key) -> size_t {
  // 将二进制表示的 1 向左移 global_depth_ 位减1,效果是将 mask 最后 global_depth_ 位 置1
  int mask = (1 << global_depth_) - 1;
  //mask 和哈希函数按位与,即只取哈希值的最后 global_depth_ 位
  return std::hash<K>()(key) & mask;  
}
//传入 key 查找哈希表,结果赋值给引用变量 value,找到返回 true
auto Find(const K &key, V &value) -> bool override;
/*
使用 IndexOf(const K &key) 获取该 key 对应的桶在目录 dir_ 中的偏移量
使用 dir_[index]-> 访问对应的桶对象后使用 Find() 查找
*/

//传入 key 查找哈希表,删除找到的 k/v,找到返回 true
auto Remove(const K &key) -> bool override;

//插入 k/v,若存在则只需要更新 v
void Insert(const K &key, const V &value) override;

对应的桶没有满时非常简单,主要是满了的时候思考几个问题:

1、分几种情况?                
全局深度大于该桶局部深度,全局深度等于该桶局部深度

2、两种情况下分裂桶有什么共同点?
所有桶中只有该桶发生改变,且只会分裂成两个桶

3、分裂后的两个桶局部深度分别是多少
都是原来的局部深度+1

4、分裂前只有一个目录项指向这个桶吗?分裂后呢?
不一定;也不一定

5、符合什么条件的目录项都指向这个要分裂的桶?
注意 IndexOf() 函数本质是截取偏移量的后几位,也就是说和插入时访问的目录项偏移量后几位相同的所有目录项都指向这个桶

6、既然可能有不止一个目录项指向这个桶,怎么寻找其它属于分裂后新桶的目录项?
和 IndexOf() 函数中用一样的方法,按位与,遍历所有目录项找到与插入时访问的目录项偏移量后几位相同的

7、目录项数有什么特点?
目录项数永远是 2 的 n 次方

8、扩容时,目录项数乘 2 ,前 n 项和后 n 项的二进制末位有什么特点?
不看第一位,后 n 项与前 n 项可以一一对应,且他们之间距离也相同

9、基于这种规律,在全局深度等于该桶局部深度时,有没有更简单的方法?
插入时的目录项指向 新建桶1,偏移量为 目录项+原有目录项数量 的目录项指向 新建桶2,
从扩容的新的偏移量开始遍历到最后,目录项直接指向 偏移量-原有目录项数量 的目录项(除上面指向新建桶2的目录项)

10、原有桶中的元素怎么重新插入?
void RedistributeBucket(std::shared_ptr<Bucket> bucket) 方法在头文件中已经提醒你了

11、分裂和插入产生分裂的 k/v 的顺序是怎样的?
先分裂,重新插入原来桶中的 k/v ,再次 insert 引起分裂的元素

12、会不会存在插入原有桶中的元素时还要分裂的情况?有无需要特殊处理的地方?
调用 RedistributeBucket() 方法后只调用 insert 方法时不需要特殊处理
加锁时使用 std::recursive_mutex 避免递归时死锁

下面给出我的 Insert 代码

template <typename K, typename V>
void ExtendibleHashTable<K, V>::Insert(const K &key, const V &value) {
  std::scoped_lock<std::recursive_mutex> lock(latch_);
  int index = IndexOf(key);
  if (dir_[index]->Insert(key, value)) {
  } else {
    // bucket full
    int global_depth = GetGlobalDepthInternal();
    int local_depth = GetLocalDepthInternal(index);
    int count = 1;
    for (int i = 1; i <= global_depth; i++) {  // 目录中指针项数
      count = 2 * count;
    }
    std::shared_ptr<Bucket> old_bucket = dir_[index];
    if (global_depth > local_depth) {
      std::shared_ptr<Bucket> new_bucket0 = std::make_shared<Bucket>(bucket_size_, local_depth + 1);  // 最高位为0
      std::shared_ptr<Bucket> new_bucket1 = std::make_shared<Bucket>(bucket_size_, local_depth + 1);
      int mask_old = (1 << local_depth) - 1;
      int mask_new = (1 << (local_depth + 1)) - 1;
      for (int i = 0; i < count; i++) {
        if ((i & mask_old) == (index & mask_old)) {  // 所有指向旧bucket的目录中指针
          if ((i & mask_new) >> local_depth == 0) {
            dir_[i] = new_bucket0;
          } else {
            dir_[i] = new_bucket1;
          }
        }
      }
      num_buckets_++;
      RedistributeBucket(old_bucket);  // 最后调用,可以处理再分配后仍需要扩展的情况
      Insert(key, value);
    } else {
      dir_.resize(2 * count);
      std::shared_ptr<Bucket> new_bucket0 = std::make_shared<Bucket>(bucket_size_, local_depth + 1);
      std::shared_ptr<Bucket> new_bucket1 = std::make_shared<Bucket>(bucket_size_, local_depth + 1);
      dir_[index] = new_bucket0;
      dir_[index + count] = new_bucket1;
      for (int i = count; i < 2 * count; i++) {  // 目录前count项指针不改变,扩展的目录项指针按之前的赋值
        if (dir_[i] == nullptr) {
          dir_[i] = dir_[i - count];
        }
      }
      num_buckets_++;
      global_depth_++;
      RedistributeBucket(old_bucket);
      Insert(key, value);
    }
  }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值