散列思想
散列表采用的是数组支持按照下标随机访问,时间复杂度是O(1)的特性。通过散列函数将元素的键值映射到数组下标,然后将数据存储到对应下标的位置。当查找数据时,通过同样的散列函数计算键值,按照键值查找数据下标对应的值即可。
散列函数
散列函数基本要求:
散列函数计算得到的散列值是一个非负整数
如key1=key2,则hash(key1)==hash(key2)
如key1≠key2,则hash(key1)≠hash(key2)
注意:第三点不是绝对的,可能存在key不同,散列函数相同的情况,这个就是散列冲突。
散列冲突
解决散列冲突的方法:
一、开放寻址法
开放寻址法的核心思想是当出现哈希冲突时,重新探测一个空闲的位置,将其插入。
1.线性探测
插入: 当像散列表中插入数据时,如果某个数据经过散列之后,存储位置已经被占用,就从当前位置开始,依次往后查找,直到发现空闲位置为止。
比如:
此时要插入一个数据X,经过散列之后X的散列值是4(也就是数组下标为4的位置),发现散列表中下标为4的位置已经被占用,则依次往后查找空闲位置,一直找到下标为7的位置(也就是69的后面)为空,则将X插入到下标为7的位置。
查找: 散列表查找的过程类似于插入,通过散列函数计算散列值,比较数组下标为散列值的位置的值是否等于要查找的值,一直遍历到空闲位置,说明不存在。
删除: 删除操作稍微复杂点,若查找到值,直接删除,会存在问题,比如说删除下标为7的值,位置就变成空闲了,此时要是进行查找(找到空闲位置查找就停止,说明不存在),就会影响下标为7以后的位置是否存在的判断。如何解决呢?我们可以不删除,将要删除的位置标记为deleted,在查找的时候遇到deleted的继续查找即可。
存在问题: 插入的数据越来越多时,空闲位置越来越少,线性探测时间越来越长,发生冲突的概率越来越大。
应用: ThreadLocal里面的ThreadLocalMap
2.二次探测
二次探测的方式和线性探测类似,线性探测每次的步长是1,二次探测的步长可以使原来的二次方或者不等于1的固定步长。
3.双重散列
使用一组散列函数,第一个散列函数计算的散列值冲突,就用第二个散列函数计算,一直到不冲突为止。
二、链表法
在散列表中,每个桶或者槽