散列表也叫哈希表(hash table),这种数据结构提供了键(Key)和值(Value)的映射关系。
只要给出一个Key,就可以高效地查找到它所匹配的Value,时间复杂度接近于O(1)。
哈希表之所以查询效率这么高,是因为有一个中转站:
本质上,哈希表也是一个数组,但数组是通过下标访问值,所以,输入的key可以从一个中转站中,通过某种方式,把Key和数组下标进行转换。这个中转站就叫作哈希函数。
在不同的语言中,哈希函数的实现方式不一样。
在Java及大多数面向对象的语言中,每一个对象都有属于自己的hashcode,这个hashcode是区分不同对象的重要标识。无论对象自身的类型是什么,它们的hashcode都是一个整型变量。
既然都是整型变量,想要转化成数组下标就不难实现了。最简单的转化方式是:按照数组长度进行取模运算。
index = HashCode (Key) % Array.length
实际上,JDK中的哈希函数并没有直接采取取模运算,而是利用了位运算的方式来优化性能。在这里可以简单理解成取模操作。
通过哈希函数,我们可以把字符串或其他类型的key,转化成数组的下标index。
如给出一个长度为8的数组,则当 key = 001121时,
index = HashCode ("001121")%Array.length = 1420036703 % 8 = 7
而当key = this时,
index = HashCode ("this")%Array.length = 3559070 % 8 = 6
哈希冲突:
由于数组长度是有限的,不同的Key通过哈希函数获得的下标有可能时相同的。当出现多个key转换成同一个数组下标的情况,叫做哈希冲突。
解决哈希冲突的方法主要有两种,开放寻址法和链表法。
1.开放寻址法:当下标已被占用,则寻找下一个空档位置。
2.链表法:Java的集合类HashMap中有用。HashMap数组中每一个元素是Entry对象,同时也是一个链表的头节点,每个Entry对象通过next指针指向它的下一个Entry节点。当新来的Entry映射到与之冲突的数组位置时,只需要插入对应的链表中即可。
哈希Map的读操作:
读操作就是通过给定的Key,在散列表中查找对应的Value。例如调用hashMap.get("002936"),意思是查找Key为002936的Entry在散列表中所对应的值。
下面以链表法讲解:
1.通过哈希函数,把Key转化成数组下标2.
2.找到下标2对应的元素,如果这个元素的Key是002936,那么就找到了;如果Key不是002936也没关系,由于数组的每个元素都与一个链表对应,可以顺着链表找,直到找到与Key相匹配的节点。
在上图中,首先查到的Entry6的Key是002947,和待查找的Key 002936不符。接着定位到链表下一个节点Entry1,发现Entry1的Key 002936正是我们要寻找的,所以返回Entry1的Value即可。
哈希Map的扩容 resize:
Capacity,HashMap的当前长度
LoadFactor,HashMap的负载因子,默认值为0.75f。
衡量HashMap需要扩容的条件:
HashMap.Size >=Capacity * LoadFactor
HashMap扩容操作:
1.扩容,创建一个新的Entry空数组,长度是原数组的2倍。
2.重新Hash,遍历Entry数组,把原来的Entry重新Hash到新数组中。重新Hash是因为:数组长度扩大后,Hash的规则也随之改变。
经过扩容,原本拥挤的散列表重新变得稀疏,原有的Entry也重新得到了尽可能均匀的分配。
扩容前的HashMap:
扩容后的HashMap:
来源:CSDN
作者:KERVEN_HKW
链接:https://blog.csdn.net/qq_34035956/article/details/104033479