前言
想的再多,不如行动起来,大家好,我是啊Q,让我们徜徉在知识的海洋里吧。
一起“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第15天, 点击查看活动详情 。
上一篇章我们介绍了 Btree和B+tree, 这一篇我们介绍一下hash索引和Hash相关的知识。
Hash
Hash的基本概念
在了解Hashing之前我们了解一下映射(Mapping)一些概念:
映射( Mapping) ****是将两个事物链接在一起的方式,称为“键”和“值”。
键(key) ——唯一标识数据(实体)的标识符。
值(value) ——我们要存储的实际实体(及其详细信息)。
举个列子:
比如每个学生都有一个学号。那么这个学号就是一个key,学生就是一个值,他可以有很多属性,比如班级,学校,学籍等等信息。 那么这个学号和学生之间就建立一种映射关系,我们通过这个学号就可以知道这个学生的一却相关信息。
那么什么是Hashing呢?
Hashing 是一种我们可以从任何键中获取整数值的方法。这个Key可能是也可能不是一个整数,但是在执行散列之后,它会为任何Key返回一个Integer值。
我们需要hash的原因是:输入中给出的键(key)不能用作放置此键值的内存存储位置。
Hashing分两步工作:
-
它以任何实体(作为key)作为输入,如果该键不是整数,我们需要通过一定的方法该实体获取一个整数值(N)
-
如果我们得到的初始值是一个整数值(N),这个 Integer 就不能用作放置这个 Key-Value 的内存地址。通过散列将此整数转换为另一个整数值,则值可用作内存地址/索引以放置此键值。
举个列子:
学生详细
信息键:学生号(studentNumber)
值:姓名、年龄、地址
我们有 3 名学生:
| 学生 | 学生号(studentNumber) | 值 |
|---|---|---|
| 学生 1 | 101 | 张三,18 岁,马里亚纳海沟 |
| 学生 2 | 105 | 李四,25 岁,珠穆朗玛峰 |
| 学生 3 | 106 | 王五,24 岁,贝加尔湖 |
由于 学生号 是 101、104、106,我们要么需要一个非常大的数组(至少 size= 106)来存储 3 个条目,要么我们可以使用一种叫做Hash Index的东西,它允许我们只使用数组 size = 3。
哈希函数会将 学生号(Key) 作为输入,并将返回hash Index作为输出。
因此哈希函数 eg 可以像 studentNumber % ArraySize 一样简单。
例如,对于 学生1:studentNumber = 101,因此此哈希函数将生成 101%3 = 2。所以学生 1可以放在size=2 处 。请注意,我们已采用 %3,因为数组的大小可以为 3(3 个学生,因此所需空间>=3)。
学生 2:105%3 = 0
学生 3:106%3 = 1
因此 Student1 可以位于数组的第二个索引处,Student1 位于数组的第 0 个索引处,Student2 位于数组的第一个索引处。
哈希表(包含学生名册号):[104, 106, 101]
如果我们想搜索一个学生,例如 studentNumber = 101 怎么办?
他将在101上运行类似的哈希函数,并将得到相同的哈希索引,即 2。
然后我们可以转到 size = 2 的数组索引位置,以获取 id = 101 的学生。
正如您所看到的,使用散列技术您只需要找到 Key 的索引,而不是查找/比较 Details(即 Values)的值。
到此处我们可以看到 Hashing索引的时间复杂度是 O(1)
hash冲突
我们接着一上面列子为列,比如现在来了一个新生:
| 学生 | 学生号(studentNumber)/键 | 学生信息/值 | hash code |
|---|---|---|---|
| 学生 1 | 101 | 张三,18 岁,马里亚纳海沟 | 2 |
| 学生 2 | 105 | 李四,25 岁,珠穆朗玛峰 | 0 |
| 学生 3 | 106 | 王五,24 岁,贝加尔湖 | 1 |
| 学生 4 | 107 | 王二,27 岁,河湾王国 | 2 |
学生4:107%3 = 2 。 可以看到,学生1的hash值和学生2的hash值都为2,这就产生了hash冲突。
解决冲突的办法如下图所示:我们将hash值相同的数据通过链表链接起来。
想象一下:如果put的数据计算的hash冲突过于严重,将导致链表过于长。这对于查询将不是好事。时间复杂都变成O(N),那么如何保存hash冲突不严重将变得重要。所以好的hash函数并不如上述简单那么简单。
Hash索引
Hash底层是由Hash表来实现的,存储引擎都会【对所有的索引列计算一个哈希码】(hash code),哈希索引将所有的哈希码存储在索引表文件中,同时在哈希表中保存指向每个数据行的指针,根据键值 <key,value> 存储数据的结构存储<哈希码,指针>,非常适合根据key查找value值,也就是等值查询(单个key查询)
性质
- 检索速度快:Hash索引能够快速定位到指定键值的数据记录,查找时间复杂度为 O(1)。
- 不支持范围查询:由于Hash索引中数据的排列是随机的,不支持范围查询。
- 适合等值查询:Hash索引适合于等值查询,即根据指定的键值进行查询。
作用
Hash索引主要用于加快等值查询的速度。在某些场景下,Hash索引的查询速度甚至可以超过B-Tree索引。
检索过程
对于指定的键值,Hash索引的检索过程如下:
- 计算键值的哈希值。
- 在哈希表中查找对应哈希值的桶。
- 在对应的桶中查找指定键值的数据记录。
需要注意的是,如果哈希冲突,即不同的键值对应相同的哈希值,那么需要进行链式存储,即在桶中使用链表等数据结构存储具有相同哈希值的数据记录。
与B-Tree的区别
相较于B-Tree索引,Hash索引有以下不同之处
- Hash索引支持快速的等值查询,但不支持范围查询,而B-Tree索引可以支持等值查询和范围查询。
- Hash索引适合于数据分布比较均匀的场景,而B-Tree索引适合于数据分布不均匀的场景。
- Hash索引的数据存储是随机的,不支持有序性查询,而B-Tree索引是基于有序的B树数据结构实现的,支持有序性查询。
- Hash索引在内存中的效率更高,因为它的查找时间复杂度是 O(1),而B-Tree索引则需要多次磁盘IO操作。