MySQL 中的HASH详解

目录

HASH表结构

HASH冲突

解决方法

链地址法

开放地址法

建立公共溢出区


MySQL中的哈希索引(Hash Index)是一种特殊的数据库索引类型,它利用哈希表(Hash Table)的数据结构来存储索引项。哈希表通过哈希函数(Hash Function)将索引列的值转化为一个固定长度的哈希码(Hash Code),然后用这个哈希码作为索引项在表中定位数据记录的位置。这种方式使得对于等值查询(例如 WHERE column = value)能够非常快速,理想情况下接近O(1)的时间复杂度。

HASH表结构

哈希表的基础结构设计主要包括以下几个关键组成部分:

  1. 哈希函数(Hash Function): 哈希函数是哈希表的核心,它的作用是将输入的键转换为一个确定的索引值,这个索引值用于决定数据在表中的存储位置。理想的哈希函数应能均匀分布不同的键值,减少冲突,并且计算速度快。常用的哈希函数有直接定址法、除留余数法、平方取中法、折叠法、随机数法等。

  2. 数组(Bucket Array): 哈希表通常由一个较大的数组构成,数组的每个元素称为一个“桶”(Bucket)。哈希函数计算出的索引值就是数组的下标,指向存放相应键值对的位置。

  3. 冲突解决策略(Collision Resolution Strategy): 当两个或多个不同的键经过哈希函数计算后得到相同的索引值,就会发生冲突。解决冲突的方法有多种:

    • 开放寻址法:在数组中寻找下一个可用的位置(例如线性探测、二次探测、双重散列等)。
    • 链地址法:在每个桶内使用链表或其它动态数据结构存储具有相同哈希值的元素。
    • 再哈希法:使用第二个哈希函数来寻找下一个槽位。
    • 建立公共溢出区:为所有冲突的元素分配一个公共的区域。
  4. 装载因子(Load Factor): 装载因子定义为哈希表中已填入的元素数量与表总容量的比例。一个合适的装载因子可以平衡查找效率与空间利用率,过高会导致冲突增多,查找效率下降。

  5. 动态调整(Resizing): 为了维持高效的查找性能,当装载因子达到某个预设阈值时,哈希表会自动调整大小,通常是扩大数组长度并重新哈希所有元素。这一过程称为重哈希(Rehashing)。

装载因子(Load Factor)

装载因子是衡量哈希表中元素填充程度的一个重要指标,计算公式为:[ \text{装载因子} = \frac{\text{哈希表中实际存储的元素数量}}{\text{哈希表的容量}} ],或者更简洁地表示为 ( \alpha = \frac{n}{m} ),其中 ( n ) 是哈希表中元素的数量,( m ) 是哈希表的容量(即桶的数量)。

装载因子反映了哈希表的饱和度。较小的装载因子意味着哈希表有更多的空闲空间,可以减少哈希冲突,提高查找效率,但同时也会浪费更多的存储空间;相反,较大的装载因子虽然提高了空间利用率,但会增加冲突概率,降低操作效率,特别是在冲突较多时,查找、插入和删除操作可能退化为链表遍历或线性查找,时间复杂度可能变为O(n)。

动态调整

为了平衡存储效率和查询效率,哈希表通常会采用动态调整机制,即根据装载因子的变化自动调整哈希表的大小。主要涉及以下两个方面:

  1. 扩容(Resizing Up): 当装载因子达到或超过一个预设的阈值(比如0.7或0.8),表明哈希表已较为拥挤,冲突增多,性能可能开始下降。此时,哈希表会自动进行扩容操作。扩容通常涉及以下步骤:

    • 新建一个更大的数组,其容量通常是原容量的两倍或更高倍数。
    • 将原有数组中的所有元素通过哈希函数重新映射到新数组中。因为容量变大,之前冲突的元素可能在新数组中找到不冲突的位置。
    • 更新哈希表的容量和装载因子阈值。
  2. 缩容(Resizing Down): 少数情况下,如果哈希表中的元素数量显著减少,为了节省空间,也可以考虑缩容。缩容的决策较为复杂,因为它涉及到效率和空间使用的权衡,而且频繁缩容可能导致不必要的性能开销。因此,实际应用中,缩容的触发条件往往设置得比较保守,或者根本不实施自动缩容,仅在必要时手动干预。

动态调整机制确保了哈希表在不同负载下的高效运行,是实现高效哈希表的关键技术之一。通过适时调整哈希表的大小,可以在保证查询效率的同时,合理利用内存资源。

HASH冲突

哈希冲突(Hash Collision或Hash Collision),也称为哈希碰撞,是指在使用哈希函数将数据(如关键字key)映射到哈希表或哈希结构中的索引位置时,两个或多个不同的数据经过哈希处理后得到相同的哈希值,从而导致它们被映射到同一个索引位置的现象。由于哈希函数的输出范围通常是有限的,而输入数据的范围可能是无限的,因此在实际应用中,特别是在较大的数据集中,哈希冲突几乎是不可避免的。

例:如下图我们依次将这些数对 12取余,将这些数添加到对应的关键字里,但是当我们添加16时,我们发现,16和4在散列表的位置冲突了,我们必须给16安排到别的位置去。

解决方法

解决哈希冲突的常用方法包括:

链地址法

链地址法(Separate Chaining)每个哈希表的槽位(bucket)存储一个链表,所有映射到该槽位的元素都放入这个链表中。这样,即使多个键值对映射到同一索引,也可以通过遍历链表来找到对应的值。

例如:

开放地址法

线性探测(Linear Probing): 发生冲突时,从发生冲突的桶开始,顺序检查下一个桶,直到找到一个空桶为止。如果达到表末尾还没找到空位,则可能需要循环回表头继续探测(称为“闭合”或“循环”探测)。这种方法简单,但可能导致数据在表中的聚集,影响查找效率。

例如:

二次探测(Quadratic Probing): 探测序列是按照1^2, -1^2, 2^2, -2^2, ...这样的平方数距离进行,即每次探测步长逐步增加。这种探测方式试图减少聚集现象,提高查找效率。

例如:

双重散列(Double Hashing): 使用两个不同的哈希函数H1和H2,当H1(key)导致冲突时,使用H2(key)来决定步长,即每次探测的位置是H1(key) + i * H2(key),其中i是递增的探查序列。这种方法可以更有效地分散冲突,减少聚集。

建立公共溢出区

当哈希表的所有槽都被填满时,可以将额外的元素放入一个单独的溢出区或链表中。这种方法简单,但是查找效率较低,因为可能需要检查两个区域。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShiningStar_Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值