Extendible Hash 举例
-
图形解释
- 图中矩形表示桶的编号(bucket_idx)的二进制形式表示,存在于目录页(Directory Page)
- 图中圆形表示桶页,其内部数据则为桶里面的内容(存在于桶页 Bucket Page)
-
假定规则
- 初始全局深度 global depth = 0
- 初始所有桶页面的局部深度 local depth = 0
- 为了方便描述,假定每个桶最多只能容纳 1 组键值对
- 当插入新数据时,如果发现桶满了,则执行桶分裂操作
- 这里采用 least significant bit (最低有效位)进行表示
-
以下是依次插入 4, 3, 6, 5 ,4个数的 Extendiable 变化图
-
最开始,目录中只有一个 bucket_id = 0 的目录项,也就是只有一个桶
-
Step1,插入数据 4 (100),桶(0)是空的,正常插入, global_depth = 1, local_depth[0] = 1
-
Step2,插入数据 3(11),末尾为1的桶不存在(相当于桶满),执行桶分裂,生成0,1两个目录项,然后重新执行 hash 操作,把 3 插入 bucked_id = 1 的桶中。
-
Step3, 插入数据 6(110),发现桶(0)已经满了(里面有4了),同时此时 glabal_depth = local_depth[0] ,则桶目录需要执行扩增同时桶页执行分裂操作(如果global_depth > local_depth[0]则 只需进行桶分裂,例如 Step 3 -> Step4过程),每次桶分裂后目录项都是原先的两倍,仔细观察会发现,每个新的目录项都是由旧的目录项通过 “bit 位左侧 +0 or + 1” 实现的,例如由 目录项0 分裂得到的新目录项分别是是 00 和 10,而由目录项目1分裂得到的新的目录项则分别是 01 和 11,仔细体会整个 +0 和 +1 的含义,实现过程则需要采用二进制运算,这样通过 +0 和 +1 得到的新页面则互称 SplitImage。注意,因为由原先的目录项 1 扩展而来的目录项 01 和 11 因为没有新数据插入,仍然指向同一个桶。
-
Step 4,插入数据 5(101),此时取 global depth (01)得到其应该插入Step3中的最下面的那个桶,而此时桶已经满了,而此时global_depth > local_depth[1] ,因此只需要进行桶分裂,不需要目录扩增,此时把该桶内的所有元素重新 hash,得到 Step4 图所示的结果。
Q & A
-
为什么采用 +0 or +1 的方式实现分桶呢?
- 以桶编号为 1为例,其二进制表示为 1, 由其分裂得到的桶项为 01, 11,可以保证分裂后的桶项和分裂前除了最高位不同,其余都相同。因为分裂完成后需要把原先满的数据重新 hash,而分裂前那个桶中的数据都是由相同的后缀构成的,再次 hash 后,能保证所有的数据都被映射到新分裂得到的两个新桶中去,如果不这个做,那么分裂后再次 hash ,数据又会被随机分配到其他桶中去,那么 hash 结果就会混乱,导致整个 hash 表失效。
-
如何得到分裂后的桶对应的编号,假设分裂前的桶的编号为 bucket_idx
// 根据桶分裂 +0, +1的规则 first = bucket_idx | (1 << global_depth) // 低位不变,把 global + 1 位变为 1 second = bucket_idx // 另一个不变
-
如何根据 bucket_idx 得到其一同分裂时的镜像桶 (Split Image Bucket)
// 把高 global_depth + 1 位置取反就可以了 bucket_idx ^ (1 << (local_depth - 1))