HashMap 面试中的 12 个点
一. 你知道哪些 map
?
HashMap
, TreeMap
, ConcurrentHashMap
, LinkedHashMap
二. HashMap
的特点是什么?
允许
Key
和Value
为null
,不过只能有一条记录Key
为null
线程不安全
无序
数据结构是 数组+链表/红黑树(JDK1.8)
三. JDK1.8 中 HashMap
为什么要引入红黑树 ?
链表 查询时间复杂度 O(n) , 插入时间复杂度 O(1)
红黑树 查询和插入时间复杂度都是 O(lgn)
可以参考下 美团技术团队 的这篇文章 https://tech.meituan.com/2016/06/24/java-hashmap.html
四. HashMap
长度为什么只能是 2 的倍数
计算
Hash
值时采用位运算来代替取模,能更高效地计算出元素的位置。
元素对应的 index 是通过下面代码赋值的 ,即 index = hash & hashmap的table长度-1
扩容时能更快地计算出
key
的index
,提高扩容效率比如 map 大小为 16 ,key 为 2 所在的 index 为 2, 18 所在的 index 也是 2
但是扩容之后变成 32 了,key 为 2 所在的 index 还是为 2, 而 18 所在的 index 就变为· 2+16=18 了。
这里 4ye 就直接截取了 resize
中 链表 重新计算 index 的部分,红黑树 TreeNode
中也有类似的代码
五. HashMap
什么时候进行扩容
当( hash 表的大小),> ( hash 表的大小 * 负载因子)的值时,则进行扩容
六. 负载因子的大小
负载因子大小决定着 哈希表的扩容 和 哈希冲突 ,每次 put **新元素 **后都要检查,看看需不需要扩容,扩容默认是原来的两倍。
调高负载因子,会增加 hash 冲突的概率 ,同样会增加耗时,扩容本身也会耗时。
七. 怎么计算 hash 值
计算 key 的 hashCode 值,然后将这个值与它的高十六位进行异或运算
这么做是为了减少 hash 冲突
验证下~
八. hash 冲突的解决办法
开放定址法
链地址法 (拉链法) HashMap 采用的
再哈希法
公共溢出区域法
九. put 和 get 的实现
put 时
先计算该 key 的 hash 值,并算出它 所在的 bucket 的 index ,如果没有碰撞的话,直接放到数组中。
如果碰撞了,先判断,这个 key 是不是同一个 key,是的话直接覆盖。
不是的话 再去判断当前是 链表 还是 红黑树,再依据不同的情况进行插入,如果 key 一样的话,会根据 onlyIfAbsent
的值 或者 原来的值是否为 null
进行替换或者保留原来的值。
如果是找到对应的 key 的话,会返回该旧值,不会继续往下执行
如果是增加新元素的话,最后会判断 hash 表的大小,如果 该值 大于 hash 表的大小 * 负载因子,则进行扩容;最后返回 null
大家可以看看 4ye 给出的这个源码,每一步都做了这个重要的注释了~ ,反正我自己看懂了 哈哈哈哈哈😝
put 源码:
get 的时候也要先计算 key 的 hash 值,然后算出它的 index,接着判断有没有 hash 冲突,没有冲突的话,直接返回,有的话需要判断当前的数据结构是链表还是红黑树,然后分别从相应的结构中取出值
这图片来自 美团技术团队 ~
十. 怎么判断一个元素是否相同的呢
先判断他们的 hash 值是否相同,相同的话再判断该 key 的 equals 方法 || == 方法是否相同,相同的话则是同一个元素
十一. 什么情况下会用到红黑树?
可以参考上面的 put 源码 ,主要代码如图:
可以看到当数组的大小大于 64 且链表的长度大于 8 时,会将链表转换成红黑树。
当红黑树的大小小于等于 6 时,会转换成链表,参考 resize
源码
十二. 头插法和尾插法
由于 jdk1.7 中 HashMap
使用的是头插法, 那么新元素总会被放在链表的头部。
比如 HashMap
大小为 4, key 2,6 ,10 所在的 index 都是 2
那么当它 扩容的时候,顺序就会变成
index:2,key:10 ,2
index:6,key:6
在多线程环境下,有可能会导致死循环~
比如 线程 a 还停留在 2.next 6,6.next 10 ,线程 b 已经完成 resize 了,变成了 10.next2 这时就会进入死循环了。
这也是 HashMap 线程不安全的原因之一。
同样的例子:尾插法不会导致死循环
如:线程 a 还停留在 2.next 6,6.next 10 ,线程 b 已经完成 resize 了,变成了 2.next10 了。
使用尾插法并不意味着 HashMap 就是线程安全了,因为你无法保证 put 进去的值,get 出来的还是 put 进去的值
,因为有可能已经被别的线程修改了。
今天就先写这么多啦,觉得还可以的小伙伴别忘了 先收藏呀,面试前看看~
最后
欢迎小伙伴们来一起探讨问题~
如果你觉得本篇文章还不错的话,那拜托再点点赞支持一下呀😝
让我们开始这一场意外的相遇吧!~
欢迎留言!谢谢支持!ヾ(≧▽≦*)o 冲冲冲!!
我是 4ye 咱们下期应该……很快再见!! 😆
如果文章对您有所帮助,欢迎关注公众号 J a v a 4 y e 😆
版权声明: 本文为 InfoQ 作者【4ye】的原创文章。
原文链接:【http://xie.infoq.cn/article/b6bda523e17ba6b2f71b0fa27】。文章转载请联系作者。
评论