还记得《猎场》中胡歌说的一句很经典的话吗?时间是一个伟大的作者,他必将给出最完美的答案!果真,不面不知道,面完后一个offer都木有...
回过头好好想想,自己两年的时间里都干了啥?看书、博客、当码农,没仔细研究过源码,看了书和博客也没有去实践、验证,最重要的是没追问这么设计到底合不合理?为啥要这么设计?依据是什么?有什么好处?
面试题:
1、hashMap有什么特性?
2、hashMap的数据结构(1、区分1.7与1.8;2、为什么使用红黑树?不采用其他的数据结构?);
3、hashMap的工作原理(put、get实现、扩容原理、扩容因子选择为什么是0.75?);
4、hash的实现,为什么要这么实现?(1、为什么一定要与高16位进行异或操作?2、对象hashCode相同会发生什么?3、为什么不直接将key作为hash值?)
5、HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变化?这种变化会带来什么问题?
6、为什么是16?必须是2的幂,如果不是2的幂会怎么样?
一、什么是HashMap?
基于hash表Map接口的实现,提供了所有可选的映射操作,并允许key和value为null
二、HashMap的数据结构?
话不多说,直接看源码好吧!值得注意的是在addEntry方法里面,它会去new Entry(),头插法就是这个地方实现的
将上面的代码,用图来更加直观的一点表示,可能会更加的清楚,建议还是看一看源码比较好一些!
看了很多博客,都说1.7的hashMap采用的头插法在多线程的情况下不安全,会有环、数据丢失的情况发生。一向严谨的我,冒着电脑冒烟的风险替你尝试并仔细分析一波,快表扬我是不是很贴心?下面就用多线程来实测一波!
当线程0进行第1499次插入时,陷入了死循环,我们用jps和jstack查看死循环情况,发现是trasfer方法中出现的问题
三、环是如何产生的?
- 假设有A、B两个线程同时执行put操作,此时map正好进行transfer扩容操作。在扩容前,map中的数据结构如下:
-
假设线程A执行时被挂起,此时e为3,next为7
-
在线程A挂起期间,线程B执行结果如下:
- 这个时候线程A继续从被挂起状态执行,此刻e.next又指向了7,形成了环
- 说实话,看了很多博客和帖子,看的头优点晕乎乎的...然后不停的画图,想了一晚上,捋了半天,现在想明白了,可以继续计算下一次循环,咱们来看看多试几次
四、数据丢失
- 数据丢失的问题,咱也假设有A、B两个线程,此时map中的数据结构如下:
- 假设线程A执行到如下位置,然后CPU时间片耗尽,线程A挂起
- 此时线程A中的map数据结构如下,需要了解一下Java内存模型
- 在线程A挂起阶段,线程B已完成数据插入,此时内存中map的数据结构如下:
- 这个时候,线程A获得CPU时间片,继续向下运行
- 此时,内存中map的数据结构如下,数据3丢失了
五、hash值计算
这里hashMap对hashCode进行了四次“扰动”,其目的为了混合高低位的差异,使其散列得更加均匀。如果直接用hashCode作为hash值是否可以?hashCode为int类型,可表示的范围为-2147483648到2147483647,大概需要40亿的映射空间,而hashMap默认大小就是16,indexFor方法中会进行hash & (length - 1)计算出对应的待插入的位置。
- 我们来看一个例子,假设我直接使用hello的hashCode作为hash值,所有高位的特征丢弃了,直接使用低4位计算index
- 这时候来了个字符串map,除第四位外其他位都不一样
- 可以看到,只要低4位相同就算其他位都不一样,也没卵用~~~ 使用了扰乱函数后,就可以混合高低位特征,尽可能的使其分布均匀。Peter Lawley的一篇专栏《An introduction to optimising a hashing strategy》的一个实验:随机选取了352个字符串,这些字符串具有唯一的hashCode,取它们的低位做掩码,计算下标:
结果表明:当使用较低的9位做掩码时,当hashMap的大小为512时,不使用扰乱函数时发生了103次碰撞,而使用扰乱函数有92次碰撞,碰撞次数减少了10%左右,可见扰乱函数还是具有一定效果的!为了降低不良散列策略的影响,hashMap采用了扰乱函数
六、HashMap扩容原理
在put值的时候,会去判断map中节点的数量是否大于等于阈值且对应的位桶不为空时触发扩容机制,当原容量不等于2^30时,新的容量变为原容量的2倍,对应的阈值也会变成原来的2倍
八、优缺点
- 优点:
(1)增删改查快:非常适合KV形式存储,由于采用了hash算法,增删改查操作非常快 - 缺点:
(1)线程不安全:不适合在多线程的环境下使用,有死循环和数据丢失的风险
(2)无序:元素存入的顺序和取出数据不一致
十、小结
主要介绍了什么是hashmap,它的数据结构是怎么样的?在多线程环境下使用hashmap会产生什么问题?分析了产生环、数据丢失的原因,hash值是如何计算的?为啥要使用扰动函数?它是如何进行扩容操作的?最后也介绍了hashmap的优缺点!
你也看到了,一开头我就把面试八股文贴了出来!说实在话,里面的很多问题虽然能答出来,但是不够理解,相信写完这系列后,那些面试题一定能对答如流的!
如果有什么不对或需要补充的,恳求大佬赐教!我不想再被面试官按在地上摩擦了,哈哈~~
参考:
mp.weixin.qq.com/s/VtIpj-uux…
blog.csdn.net/gongsenlin3…
zhuanlan.zhihu.com/p/135325790
www.javacodegeeks.com/2015/09/an-…