可扩展Hash

可扩展Hash

可扩展hash (Extendible Hashing ) 是一种动态 hash 方法. 可扩展 Hash 是从链式 hash 方法延申的. 链式 Hash 也是动态 hash 方法, 示例如下:

链式 hash 中, Directories 大小是固定的, 当不同的元素的 key 映射到相同的 Directories 的 slot 之后, 相同的Key会放入相同的 Bucket, Bucket 的数据结构有很多种形式, 通常是链表, Bucket 没有大小限制, 随着元素的写入无线增大. Bucket 的无限扩张会导致随着元素的写入, 查找效率越来越低. 因此一种优化的方式为可扩展 Hash 的数据结构.

可扩展Hash 的特点

  1. Hash 函数也会随着数据结构的调整而动态调整.
  2. 可扩展Hash的 Directories 与链式 Hash类似, Directories 中的每一个slot存储的是 Bucket的起始地址, 可扩展Hash的一大特点就是它的Directories 会随着元素的写入而改变大小, 但是 Directories 的大小一定是 \(2^{global\_depth}\).
  3. Buckets: 可扩展Hash的Buckets是存储数据 value 的地方, 但是通常还需要存储这个Bucket的大小等信息. 通常实现的方式是链表.

可扩展Hash的数据结构

可扩展Hash的主要结构示意图如下:

可扩展Hash数据结构的解释

  1. Directories: 可扩展Hash的Directories与链式Hash类似, 目录的每一项存储着一个 Bucket的地址. 不同点在于可扩展Hash的Directories更像是一张表, 每一个表项是可以用下标检索的. 并且Directories的大小为 \(2^{global\_depth}\) . 每次当可扩展Hash表新插入元素的时候, Directories都有可能扩充, 此时 Directories的结构会发生改变, 每一个slot的下标index会改变.
  2. Buckets: 可扩展Hash表的Buckets 也是用来存储数据value的. 目录中的 slot 指向 Bucket, 每一个Bucket有一个对应的局部深度(Local Depth), 当该Bucket的局部深度小于全局深度(Global Depth)的时候, 目录中会有多个 slots 指向该 Bucket. 可扩展Hash的每一个Bucket有一个大小限制, 当一个Bucket中存储的value的个数大于这个限制后, 这个Bucket会分裂.
  3. Global Depth: 全局深度Global Depth 主要是 Hash 函数用于将 Key 分到不同的目录中的. 简单的说, 目录id 由Global Depth位表示. 例如Global Depth为3, 那么目录的slots的个数是 8个, 索引index的位数是 3位.
  4. Local Depth: 每一个Bucket都有自己的Local Depth, Local Depth 表示使用目录的index的最后几位用于指向该Bucket, 例如一个Bucket的Local Depth 是2, 而 Global Depth是3, 那么index为 1, 5 都有可能指向该Bucket.
  5. Bucket Splitting: 初始化可扩展Hash表的时候, 会设置一个Bucket大小限制, 当写入的value超过Bucket的大小限制的时候, Bucket会分裂, 此时, 分裂的Bucket的 Local Depth 会增加 1, 如果增大后的 Local Depth 大于 Global Depth, 将会触发 Directory Expansion.
  6. Directory Expansion: Local Depth 表示的是使用index的 Local Depth 位索引到某一个Bucket. 当某次Bucket 分裂之后, Local Depth 大于 Global Depth, Global Depth 必须增加, 此时目录大小增大, 因为目录的大小为 \(2^{global\_depth}\), 目录变为原来的两倍. Hash 函数会改变, 目录指向的 Bucket的指针也会发生改变.

可扩展hash(Extendible Hashing)工作流程

可扩展Hash的实际工作流程很简单, 需要关注的是上述所属的 Bucket Splitting(桶拆分) 以及目录扩张(Directionary Expansion) 的过程.

  1. 分析数据类型, 可扩展Hash支持多种数据类型, 使用 Hash 函数将数据转换为整数类型(通常是整数类型) 作为 Key. 这个Key不可以直接作为 Dictionaries 的Index的索引, 需要通过下面的计算.
  2. 将 Key 转化为二进制的表达形式. 例如我们计算得到的 Key 是49, 二进制表示是 110001.
    3.获取Global Depth: 假设我们的 Global Depth 是3.
  3. 确认目录的Index: 目录的slot的ID(index), 使用key的二进制数的Global Depth LSB. 如果Global Depth为3, 那么就是 3LSB. 这里110001的3LSB为 001.
  4. 获取目录中slot指向的 Bucket, 这里 001 指向第一个 Bucket.
  5. 向 Bucket 中插入元素, 并检查是否溢出(Bucket的大小是初始化的时候固定的). 如果溢出, 参考步骤6, 不溢出表示插入数据成功.
  6. Bucket中插入数据后溢出: 获取Local Depth. 将 Bucket 分裂, Local Depth增加1. 此时判断 Local Depth 与 Global Depth 之间的关系.
    a) 如果Loacl Depth 加一之后小于Global Depth: 将旧的Bucket一分为二后, Global Depth+1, 因此指向这两个Bucket的索引的个数改变(Global Depth 是Index指向Bucket索引的最后的位数). 更新目录指向Bucket的索引.
    b) 如果Loacl Depth 加一之后大于Global Depth: 这种情况Global Depth需要增加 1, 目录扩充为原来的两倍. 此时原来指向某个Bucket的slot会有可能变成两个, 例如index为 01 变成001和101, 这两个 slot 均指向这个Bucket. Splitting Bucket会被分成两个, 两个不同的slot分别指向两个Bucket.
  7. 重新分配原来的Hash的Directionaries, 更新Hash 函数.

使用实例

我们用下面的例子来说明可扩展Hash的工作流程.
假设我们的插入的元素形成的Key的序列为 [16,4,6,22,24,10,31,7,9,20,26].
我们的 Bucket Size 大小为 3.
从Key的序列到目录的Index使用的是 X LSBs函数. 使用二进制的最后的X位表示目录的Index.
首先, 我们得到上述序列的二进制表示, 如下:

16- 10000 
4- 00100 
6- 00110 
22- 10110 
24- 11000 
10- 01010 
31- 11111 
7- 00111 
9- 01001 
20- 10100 
26- 11010 

初始状态如下图的图1所示:

  1. 初始状态下, Global Depth 与 Local Depth 均为 1. 还没有元素存入到Bucket中.
  2. 当我们顺序插入三个元素后, 16, 4, 6, 它们二进制的最后一位都是0, 所以插入第0个Bucket(Local Depth为1), 继续插入元素
  3. 当插入元素 22 之后, Bucket的大小为4, 超过Bucket的大小限制, 该Bucket分裂, 一分为二. 分裂之后, 分裂的两个Bucket的Local Depth为2, 大于Global Depth, Global Depth也需要加一, 变为2, 然后目录 Directories 扩充, 变为原来的两倍.
  4. 由于分裂的Bucket的Local Depth为2, 所以使用Index的两位作为索引, 因此00和10分别指向分裂的两个Bucket, 分裂之前的Bucket的Index为0, 新增一位后就是 00 和 10. 原来Index为 1的Bucket没有分裂, 它的Local Depth为1, 所以 01 和 11 的Index均指向该Bucket.

  1. 分裂之后我们继续写入, 该示例中, 在下图的图2到图3的过程中, 当插入到元素20的时候, Index 00 指向的Bucket又超过了Bucket的限制大小, Bucket分裂, 一分为二. 此时 Local Depth增加到3, 所以Directories扩充, 变成原来的两倍. 000指向分裂的第一个Bucket, 100指向分裂的第二个Bucket. 原来Local Depth为2的Bucket会有两个slot指向它, 分别是 010110. 在这个目录的所有slot中, 最后一位为 1 的slot均指向最后一个Bucket, 它的Local Depth为 1.
  2. 接下来从图4到图5就很容易解释了, Local Depth 增加, 但是小于等于Global Depth, 所以可以直接修改Directories 的slot的Index的指向, 而实际上, Hash函数也需要做对应的修改.

总结

上述是我对可扩展Hash的理解, 我参考了一些文章, 我觉得有一点不同的就是, 我将Directories的slot的Index作为Key的一部分, 也就是说我的value通过Hash函数得到Key, 然后通过Key计算得到对应的Index, 然后找到对应的Bucket, 但是也有很多介绍中, 选择将value直接二进制化, 这个二进制的value的一部分直接作为slot的Index.

posted @ 2024-07-15 13:41  虾野百鹤  阅读(4)  评论(0编辑  收藏  举报