可扩展Hash

可扩展Hash

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

使用实例

我们用下面的例子来说明可扩展Hash的工作流程.
假设我们的插入的元素形成的 Hash值 的序列为 [16,4,6,22,24,10,31,7,9,20,26].
我们的 Bucket Size 大小为 3.
从 Hash值 的序列到目录的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.

img

  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函数也需要做对应的修改.
    img

总结

上述是我对可扩展Hash的理解, 我参考了一些文章, 总结来说, 可扩展Hash的Hash值计算部分和普通的 Hash表是一样的, 不同点在于可扩展 Hash 计算得到Hash值是作为一个索引, 用于存储键值对, 也就是说数据存储的构造方式不同, 这也就是为什么可扩展 Hash 可以用于数据库存储.

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