HashMap底层实现原理

HashMap是我们常用的数据结构,是一种有由数组和链表组成的数据结构
数组里每个地方都保存了Key-Val这样的实例,JAVA7叫Entry,JAVA8叫Node
但是它的本身就是Null,所以在put进行时,会根据key的hash去计算一个indext值(也就是插入位置)
hash具体是怎样计算的呢
源代码里是这样实现的
hash=(hash>>16)^hash
index=(array.length-1)&hash

我们需要得到合法下标,必须在[0,array.length)中,不可以直接传入hash作为index,
有时候可能会想我们可以直接通过hash%array.length得到合法下标。但是这种操作
相比较mod操作来说太慢了,所以我们使用上述方式。
但是这种方式有一个条件那就是array.length必须是2的n次方,比如说 16 32 64 128
又因为只进行index=(array.length-1)&hash的话,能被利用的只有后面几个二进制位,可能会导致下表不均匀,所以说我们需要hash=(hash>>16)^hash右移16位之后进行异或操作,使得下标尽可能均匀。
这里再来谈一谈数组的扩容,因为随着不断地Put进去Key,我们的初始数组容量是不够的,在HashMap底层扩容机制是这样的,它设置了负载因子,
负载因子=Key的size/数组的length
阙值是0.75,
因为 根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。
我们在阙值到来的时候必须降低负载因子所以我们就要扩容
在这里提一下,这里的扩容和顺序表的扩容是不一样的
顺序表的扩容是为了解决放不下的问题
哈希表的扩容是为了解决冲突的问题
直接在原有基础上扩容两倍,总体步骤是这样的
1.扩容:创建一个新的Entry数组,长度是原来数组的两倍
2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
这里我们第一感觉就是,为什么还要重新Hash呢???直接复制过来不香吗
对于这个问题我们就得回到之前的公式index=(array.length-1)&hash
长度变化了所以位于出来的自然就不一样了!!

我刚才提到了链表,为什么会有链表,因为我们知道在有限的长度里使用哈希,哈希本质就带有随机性,我们两个Key得到了相同的hash就形成了链表,我们以这种方式解决哈希冲突。这里说一下冲突不剧烈的情况下整体时间复杂度是0(1),因为链表冲突不剧烈很短可以看作O(1),O(1)+O(1)=O(1)
我们再来说一说插入链表时候的操作,总体是这样的JDK8之前都是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。,JDK8之后就变成了尾插法。
这是为什么呢???
因为HashMap在多线程环境下,运行多个线程插入时,我们在没resize前就插入完成,这就会导致,因为resize的赋值方式,也就是使用了单链表的头插方式,同一位置上的新元素总会被放在链表的头部位置在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。这就有可能会出现环形链表
如果这时候去取值就会报错。
运用头插之后就没有这种问题了,这里再说一下JDK8链表超过8就会变成红黑树,这里巧妙地将时间复杂度,0(n)变成了O(logn).关于这个数据给狗我过几天复习数据结构的时候再来阐述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值