哈希表和哈希函数


什么是哈希表

哈希表(hash table)也叫作散列表,是存储Key-Value映射的集合。对于某一个Key,散列表可以在接近O(1)的时间内进行读写操作。散列表通过哈希函数实现 Key和数组下标的转换,通过开放寻址法和链表法来解决哈希冲突。散列表这种数据结构提供了键(Key)和值(Value) 的映射关系。只要给出一个Key,就可以高效查找到它所匹配的Value,时间复杂度接近于O(1) 。

什么是哈希函数?

本质上哈希表其实也是数组,数组只能根据下标,像a[0]、a[1]、 a[2]、a[3]、a[4]这样来访问,而散列表的Key则是以字符串类型为 主的。需要一个“中转站”,通过某种方式,把Key和数组下标进行转换。这个中转站就叫作哈希函数。

哈希函数的实现

在不同的语言中,哈希函数的实现方式是不一样的。以Java的常用集合HashMap为例。 在Java及大多数面向对象的语言中,每一个对象都有属于自己的hashcode,这个hashcode是区分不同对象的重要标识。无论对象自身的 类型是什么,它们的hashcode都是一个整型变量。 既然都是整型变量,想要转化成数组的下标也就不难实现了。最简单的转化方式是什么呢?是按照数组长度进行取模运算。

index = HashCode (Key) % Array.length

实际上,JDK(Java Development Kit,Java语言的软件开发工具包)中 的哈希函数并没有直接采用取模运算,而是利用了位运算的方式来优化性能。不过可以姑且简单理解成取模操作。通过哈希函数,我们可以把字符串或其他类型的Key,转化成数组的下标index。

如给出一个长度为8的数组,则当 key=001121时,

index = HashCode (“001121”) % Array.length = 1420036703 % 8 = 7

而当key=this时,

index = HashCode (“this”) % Array.length = 3559070 % 8 = 6

哈希表中的读写操作

写操作

  • 第1步,通过哈希函数,把Key转化成数组下标5。
  • 第2步,如果数组下标5对应的位置没有元素,就把这个Entry填充到数 组下标5的位置

读操作

什么是哈希冲突

在散列表写操作时,由于数组的长度是有限的,当插入的Entry越来越多时,不同的 Key通过哈希函数获得的下标有可能是相同的。例如002936这个Key对应的数组下标是2;002947这个Key对应的数组下标也是2。这种情况,就叫作哈希冲突

解决哈希冲突的方法

  • 开放寻址法,

    开放寻址法的原理很简单,当一个Key通过哈希函数获得对应的数组下 标已被占用时,我们可以“另谋高就”,寻找下一个空档位置。

  • 链表法。

    Java的集合类HashMap当中。 HashMap数组的每一个元素不仅是一个Entry对象,还是一个链表的头节 点。每一个Entry对象通过next指针指向它的下一个Entry节点。当新来的 Entry映射到与之冲突的数组位置时,需要插入到对应的链表中

链表法比较重要,所以讲讲链表法实现步骤如下

  • 第1步,通过哈希函数,把Key转化成数组下标2。
  • 第2步,找到数组下标2所对应的元素,如果这个元素的Key是002936, 那么就找到了;如果这个Key不是002936也没关系,由于数组的每个元素都与一个链表对应,可以顺着链表慢慢往下找,看看能否找到与 Key相匹配的节点。

注意:关于HashMap的实现,JDK 8和以前的版本有着很大的 不同。当多个Entry被Hash到同一个数组下标位置时,为了提升插入和查找的效率,HashMap会把Entry的链表转化为红黑树这种数据结构。由于散列表的具体实现代码比较复杂,所以有兴趣自己去看看源码吧嘿嘿。

哈希算法

Hash算法是一个广义的算法,也可以认为是一种思想,使用Hash算法可以提高存储空间的利用率,可以提高数据的查询效率,也可以做数字签名来保障数据传递的安全性。所以Hash算法被广泛地应用在互联网应用中。


文章作者: 冰冰的小屋
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 冰冰的小屋 !
  目录