面试官看过来!HashMap就应该这么问!

HashMap

往期推荐
java基础:震惊!这么简单的java基础题竟然都做错了!!
网络IO:因为取了个快递我搞懂了五种网络IO模型
外包面试题:软通面试题

小要点

  • 线程不安全
  • 继承 AbstractMap类,实现map接口
  • k/v可以为null

底层变化
​jdk7:Entry数组➕链表
jdk8:Node数组➕链表➕红黑树

jdk8 put过程:当程序视图将一个key-value放入HashMap中时,程序首先根据该key的hashCode()返回值决定该Node的储存位置,如果两个Node key的hashCode()返回值相同,那么他们的储存位置相同。如果两个的Node的key通过equals比较返回true,新添加的Node的value将覆盖集合中原有Node的value,但key不会覆盖。如果返回false,新添加的Node将于集合中的原有Node形成Node链,而且新添加的Node位于Node链的尾部,当链表中的元素超过8个的时候,将会转变成红黑树形式

HashMap的链表变化

​ jdk7头插法

​ 在多线程环境下,一次扩容前后,A线程更改的链表为A-B,这时挂起A线程,进行扩容,因为是头插法,B线程会将链表变成B-A,这个时候线程A在执行操作,会回发生环状链表,再使用get查询,会导致死循环

总结:头插法在扩容的时候会改变原有链表的位置顺序,将原本的引用关系颠倒,形成环状链表,导致死循环

jdk8尾插法
扩容转移后前后链表顺序不变,保持之前节点的引用关系,所以解决了头插法带来的死循环问题

TODO: 讲解正在路上 🚗 🚗。。。

HashMap的扩容机制resize

属性

  • loadFactor:负载因子0.75

Q:为什么负载因子为0.75f
A:当负载因子为1的时候,空间虽然利用率最高,碰撞会比较严重
当负载因子为0.5的时候,碰撞会减小,但是空间利用率变小
所以折中取值0.75,与数组容量相乘还是个整数,完美!

  • capacity :当前数组容量
  • 扩容的阈值 = capacity * loadFactor,达到阈值进行扩容

扩容两步
​ 1.创建一个新的数组,数组长度是原先当两倍
​ 2.遍历原来的数组,将其重新计算hash并存放到新数组

重新计算hash的原因:因为数组长度有变化,所以之前的hash规则变化,需要重新计算

​HashMap为什么说线程不安全

原因:
​ jdk7会发生循环链表死循环(上面刚讲过👆)

​ jdk8会发生数据覆盖:在put的过程中,AB两个线程同时判断该位置为null,当A进行插入当时候挂起,B开始进行插入,插入成功后,A无需在判断是否为null,所以再次进行插入,这个时候会将原有的数据覆盖而不是形成链表

TODO: 补源码!

为什么HashMap初始长度为2的幂次方

默认容量:代码中展示 1 << 4 也就是16

自定义容量:HashMap根据用户传入的初始化容量,会利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂的值,用作于初始容量

原因:代码:h & (length-1);为了减少hash碰撞,所以将hashcode转化成下标的时候采用的是取模方式,但是hashmap采用的位运算代替了取模,这样效率会更高,因为采用了位运算实现取模运算,所以数组容量为2的幂次方

如何减少hash碰撞

  • 链地址法:将hash地址相同的元素构成一个单链,将产生冲突的值以链表的形式连起来
  • 红黑树:引进红黑树降低遍历复杂度为O(logn)
  • 扰动函数 :上面说了,存放地址是按照位运算取模找到的,但是这样的扰动还不够,所以他们还使用了扰动函数(hashcode ^ hashcode >>> 16),继续增加值的不确定性,来降低哈希冲突

红黑树转化四问

Q:什么时候链表转化成红黑树
A:链表的长度达到了8,并且数组的长度大于等于64会转化成红黑树


Q:为什么转换红黑树
A:在使用良好当散列码的时候我们并不需要进行转化成红黑树,当链表长度很小的时候,因为即使遍历链表,速度也非常快,
所以当出现不合理的情景出现,我们需要转化成红黑树,虽然红黑树占用的空间是链表的两倍,但是链表的复杂度为O(n),红黑树的复杂度为O(logn),所以当链表长度不断变长,肯定会对查询性能有一定的影响,所以才需要转成红黑树。

Q:为什么转化成红黑是的阈值为8
A:节点(链表)的分布频率会遵循泊松分布,链表长度为8的概率很小,概率为0.00000006,几乎是不可能事件,如果出现这种概率的话,肯定是出现不合理的场景了,所以这个时候转化更能提升效率


Q:​为什么转化成链表的时候为6
A:之所以互相转换的阈值不同,是因为防止他们频繁的转化

HashMap如何遍历循环

map.entrySet()

for (Entry<String, String> entry : map.entrySet()) {
	entry.getKey();
	entry.getValue();
}

map.keySet()

for (String key : map.keySet()) {
	map.get(key);
}

map.entrySet()的集合迭代器

Iterator<Map.Entry<String, String>> iterator = 
map.entrySet().iterator();
while (iterator.hasNext()) {
  entry.getKey();
  entry.getValue();
}

以上就是总结出来的关于HashMap的相关问题
错误&缺少的地方希望大佬们指出🙏🙏🙏


另外我给大家提供了资料大礼包一份(共2G),大家可以关注我的公众号(在最下方!),回复:java面试题 来领取
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值