谈谈你对HashMap的理解
1.初始长度16,内部采用了tableSizeFor方法来保证容量为2的N次方。(引申问题:为什么是2的N次方)
2.扩容因子0.75。
3.线程不安全。(扩展:HashMap如何实现线程安全)
4.数据结构jdk1.7中是数组+链表,jdk1.8之后是数组+链表+红黑树。当数组长度大于64并且链表长度>8的时候将链表转换为红黑树提高查询效率。(扩展:为什么要采用红黑树?)
5.内部计算节点hash值的时候采用了 key.hashCode()^ key.hashCode()>>>16来进行计算。原理是将hash值的高位做为扰动因子,用hash的高位与低位进行与运算达到减少哈希碰撞的效果。
6.HashMap可能会出现内存泄漏。我们在使用的时候如果需要改变存在HashMap中某个元素的某个属性值,那么需要重写hashcode方法,使用不可变的属性来计算hash。
7.hashMap的扩容是通过 数组容量 * 扩容因子 得到一个threshold阈值,当Map中的元素个数(size)>threshold时发生扩容,扩容会创建一个新的Node数组,容量是旧数组的2倍。遍历旧数组中的每一个元素,分三种情况进行处理:
- 第一种情况:如果链表中只有一个元素,那么采用e.hash & (newCap - 1) 来计算该元素在新数组中的位置(元素的哈希 & 新数组容量-1)。
- 第二种情况:如果当前元素是一个二叉树的节点,那么遍历二叉树,通过计算(e.hash & bit) 是否等于 0来将二叉树拆分成高位和低位两个二叉树(这里的bit是旧数组的容量)。然后分别判断高位和低位二叉树的节点个数是否小于6,如果小于6那么重新转为链表,最后对于低位二叉树赋值到原数组位置,对于高位二叉树赋值到原数组位置+原数组容量的位置。
- 第三种情况:对于链表中有多个元素且没有被转成二叉树的情况,遍历链表,通过计算(e.hash & oldCap)是否等于0来拆分成高位和低位两个链表,最后对于低位二叉树赋值到原数组位置,对于高位二叉树赋值到原数组位置+原数组容量的位置。处理与第二种情况相同。
(扩展:为什么使用高低位算法?)
从hashMap中,我们能学到的设计:
1.通过key.hashCode()^ key.hashCode()>>>16 来减少哈希冲突。
2.通过高低位算法来减少哈希冲突。
3.对于长链表,我们可以通过红黑树来提高查询效率。
大家好,我是小羊炒饭。感谢大家的关注,有任何问题可以提出,我们一起学习共同进步。从今天开始每周至少会有一篇纵向学习和分析java代码和框架的文章。公司的前辈告诉我,想涨工资人情>技术,我不懂人情,所以我想深耕技术,希望有一天能涨工资哈哈。
扩展文章可以参考我写的其他文章 (没有的就等一等哈哈,也可以自己百度)