底层实现原理:
HashMap
底层数据结构在Java8
之前是数组+链表
的形式,在java8之后是数组+链表+红黑树的形式;
当创建时默认长度为0,调用put方法时长度会进行首次扩容长度为16,当我们里面的元素个数/总容量
超过默认负载因子0.75时就会进行扩容,每次扩容2倍;
当添加对象时首先会对key进行hashCode()
运算,再调用HashMap
的hash()
方法进行二次哈希,最后在通过取模数组长度计算出桶下标在jdk1.8后优化了取模计算方式
,如果当前下标内容为空则直接添加,不为空时则判断key的hashCode()
是否一致,如果不一致则向后追寻,直到下标内容为空时添加jdk1.8是尾部追加,jdk1.8之前是头部追加
,如果一致则在进行equals
运算判断内容是否相同,相同则将value进行替换,不同则在和下面的内容继续比较;
添加后当前链表长度大于8且数组容量大于等于64时则将链表转为红黑树,追加完毕或转为红黑树后如果数组内的元素个数大于数组长度*负载因子
则调用resize()
进行扩容,并将所有桶下标进行重新计算重新取模当前新的容量计算桶下标
。
拔高题:
底层数据结构
- java8之前的底层数据结构为数组+链表
- java8之后的底层数据结构为数组+链表+红黑树
为什么要用红黑树?
因为不用红黑树的话如果去访问一个数据时如果链表很长我们就要从头开始比较,会影响HashMap的性能时间复杂度为O(n)
,当使用红黑树时他会按照大小进行分配(比父节点大的放在右边,比父节点小的放在左边),我们只需要通过哈希码(如果哈希码相同比较字符串值)比较大小即可省略掉多余的比较定位到我们需要找到的元素时间复杂度为O(log₂ⁿ)
。
为什么一上来不转为红黑树?
因为一开始链表长度短的时候他的性能是要比红黑树好的,只有链表长的时候性能才没有红黑树好。
并且链表的数据结构为Node,红黑树的数据结构为TreeNode,TreeNode中的成员变量要比链表多很多,所以红黑树占用的内存也比链表要多。
为什么树化阈值为8?
正常来说如果hash足够随机并且负载因子为0.75的情况下,HashMap中链表长度超过8的情况几乎没有,选择8就是让转化为红黑树的几率更小,之所以需要转换红黑树是为了避免DoS攻击,防止链表超长时性能下降,树化应当是偶然情况。
什么时候会变为链表?
当长度小于等于6时会转为链表
remove
红黑树节点时,若root,root.left,root.right,root.left.left 有一个为 null,也会转为链表root表示根节点,left表示左边的节点,right表示右边的节点
。
索引下标如何计算?
1.8之前:二次哈希值取模数组长度。
1.8:计算对象的hashCode(),再进行调用HashMap的hash()方法进行二次哈希,最后&位运算
(capacity当前容量
- 1)得到索引。
为什么要二次哈希?
如果HashCode低位值都一样,不进行二次hash的话就会发生hash分布不均匀的问题,为了保证数据在数组中分布均匀,避免链表过长的情况,我们需要进行二次hash运算。
数组容量为何是2的n次幂?
计算索引时,如果是2的n次幂可以使用位与运算代替取模,效率更高;扩容时使用 二次hash & oldCap旧的容量
==0的元素留在原来位置,否则移动到新位置旧位置+oldCap
。
如果数组容量不是2的n次幂会怎么样?
上面说的都是为了配合容量为2的n次幂时的优化手段,例如Hashtable的容量就不是2的n次幂,并不能说哪种设计更优,应该说设计者综合了各种因素,最终选择了使用2的n次幂作为容量。
如果我们数组中存储的都是偶数则2的n次幂的容量会导致全部分布在偶数的位置。
如果你想追求更高的效率的话可以使用2的n次幂作为容量,如果想要更好的 hash 分布性可以选择质数作为容量。
put方法在jdk1.7和jdk1.8的区别是什么?
当链表插入节点时,1.7是头插法,1.8是尾插法;
1.7是达到阈值且当前计算下标位置的内容不为空时才扩容,而1.8是超过阈值就扩容;
1.8在扩容计算Node索引时,会优化扩容时 hash & oldCap
旧的容量==0的元素留在原来位置,否则新位置=旧位置+oldCap。
;
加载因子为何默认是 0.75f?
- 在空间占用与查询时间之间取得较好的权衡
- 大于这个值,空间节省了,但链表就会比较长影响性能
- 小于这个值,冲突减少了,但扩容就会更频繁,空间占用多
HashMap多线程下会有什么问题?
jdk 1.7时会出现扩容死链的问题;
多线程写入操作值覆盖的问题;
key是否可以为null?作为key的对象有什么要求?
HashMap的key可以为null,但Map的其他实现则不然;
作为key的对象,必须实现hashCode和equals,并且key的内容不能修改(不可变);