Extendible Hashing
Background
最近在学习CMU 2021的15-445课程, 其中lab2便是要实现一个Extendible Hash, 实验过程踩了不少的坑,在这里记录一下。
Extendible Hashing
- 正如其名, 是一种可扩展哈希表, 可扩展指其容量,因为当不停往哈希表插入键值对时, 如果纵向(纵向是哈希表目录 director 大小, 横向是每个键对应的同一键池 bucket 大小)大小不扩张, 只扩张横向大小, 导致冲突键值对太多,寻找效率退化为 O(n)
- 显然扩展后的哈希表需要再hash一遍使键值对放在新哈希表对应的地方,如果表内元素过多,那么这将是一件非常耗时的事(但redis就是这么做的),所以就有了 Extendible Hashing, 它每次只rehash局部的键值对, 满足了可扩展的同时也减少了rehash时间
- extendible hashing 主要组成为四部分
- 键值桶 bucket : 存放所有键相同的键值对
- 目录 director : 存放 key->bucket 映射关系
- 全局深度 global depth :表示使用 key 的 低 global depth / 高 global depth 位来在目录中寻找映射关系
- 局部深度 local depth :每个 bucket 拥有一个 local depth, 表示当前 bucket
- 到这里可能会迷惑为什么有两种深度, 但我们看一下它是怎么扩展的就明白了
取值操作
- 设全局深度为 n, 取 key 的 低n位 q, 使用 q 在目录中取得映射关系 q->bucket, 在对应bucket中寻找匹配
扩展操作
- 触发条件 :
- 当一个 bucket 满时就触发一次扩展操作
- 扩展过程, 假设当前全局深度为 n,需要扩展的 bucket 局部深度为 m, key 的低 m 位为 d、低 m+1 位为 p
- p 最高位取反得到 q
- 分配一个新 bucket
- 将 旧 bucket 中键值对,键的低 m+1 位为 q 的键值对取出放入新 bucket
- 若 m+1 > n, 则更新 n = n + 1, 此时目录扩展一倍, 将目录上半部分信息(局部深度,映射关系)复制到下半部分
- 遍历目录key **(1 到 (1 << n)) **
- 低 m+1 位为 p 的, 局部深度更新为 m+1
- 低 m+1 位为 q 的, 局部深度更新为 m+1, 映射关系更新为 key->新bucket
合并操作
-
触发条件:
- 当前 bucket 为空时触发合并操作
-
合并过程, 设当前 bucket id 为 b, 局部深度为 m, key 低m位 为 p:
- p 最高位取反得 q
- 在目录中寻找 q 的映射关系, 若 q 对应的局部深度也为 m , 表明改兄弟目录未再次扩展, 可以合并, 否则不能合并
- 获得 q 的 bucket id d
- 遍历目录key **(1 到 (1 << n)) **
- 低 m 位为 q 的, 局部深度更新为 m-1
- 低 m 位为 p 的, 局部深度更新为 m-1, 映射关系更新为 key->d
- 若全部 local depth 都小于 global depth 则更新 global depth = global depth - 1
小于 global depth 则更新 global depth = global depth - 1
参考
Extendible Hashing (Dynamic approach to DBMS) - GeeksforGeeks