hash冲突

155 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

名词解释

  • 哈希表:是一种实现关联数组抽象数据类型的数据结构,这种结构可以将关键码映射到给定值。简单来说哈希表(key-value)之间存在一个映射关系,是键值对的关系,一个键对应一个值。

  • 哈希冲突:当两个不同的数经过哈希函数计算后得到了同一个结果,即他们会被映射到哈希表的同一个位置时,即称为发生了哈希冲突。简单来说就是哈希函数算出来的地址被别的元素占用了。

哈希冲突的四种解决方法

链式地址法(拉链法)

将所有哈希地址相同的记录都链接在同一链表中。

再哈希法

同时构造多个不同的哈希函数,等发生哈希冲突时就使用第二个、第三个……等其他的哈希函数计算地址,直到不发生冲突为止。虽然不易发生聚集,但是增加了计算时间。

线性探测法

发生冲突,往后加1一直到空闲位置

二次探测

冲突发生时,在表的左右进行跳跃式探测,比较灵活。

应用

golang map 中的哈希

  • golang里的map是一个kv对集合。底层使用hash table,用链表来解决冲突。出现冲突时,以bmap为最小粒度挂载,一个bmap可以放8个kv。哈希函数选择,可以支持aes采取aes,否则使用memhash。

  • bucket:8个大小,data的byte字段存储kv的值,节省字节对齐带来的空间浪费;overflow记录溢出的bucket地址,将所有冲突的键连接起来。

  • 哈希冲突:链地址法。overflow指向了一处部分,降低了存储效率。

  • 负载因子的含义和影响:

    • 含义:衡量一个哈希表冲突的情况,负载因子 = 键数量/bucket数量

    • 哈希表需要将负载因子控制在合适的大小,超过其阈值的需要进行rehash

      • 过小说明空间利用率低;过大说明冲突严重,存取效率低
      • 哈希表实现的不同导致容忍度不同,redis的负载因子时1就出发rehash,go由于bucket可以装8个键值对,所以可以容忍更高的负载因子6.5
  • map扩容:

    1. 负载因子 > 6.5时,也即平均每个bucket存储的键值对达到6.5个。

      • 当负载因子过大时,就新建一个bucket,新的bucket长度是原来的2倍,采用渐进式reshash,每次搬迁2个键值对。
    2. overflow数量 > 2^15时,也即overflow数量超过32768时。

    3. 等量扩容:buckets数量不变,重新做一遍类似增量扩容的搬迁动作,吧松散的键值对重新排列一次,以使bucket的利用率更高,保证更快的存取

redis hash冲突

  • 使用链式哈希来解决,即同一个哈希桶的多个元素用一个链表来保存,它们之间依次用指针连接

    • 数据量增大,链表长度增长,查询速度变慢
  • 对哈希表做rehash操作,增加现有的哈希桶数量。Redis维护了两个全局哈希表。

    • rehash步骤1:给哈希表2分配更大的空间
    • rehash步骤2:把哈希表1中的数据重新映射并拷贝到哈希表2中
    • rehash步骤3:释放哈希表1的空间
    • 缺点:涉及到大量数据拷贝,会造成线程阻塞,无法服务其他请求
  • 渐进式rehash:第二步拷贝数据时,redis仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;将一次性大量的拷贝开销,分摊到了多次处理请求中,避免耗时操作,保证数据的快速访问

参考文献 blog.csdn.net/qq_48241564…