1.HashMap 的数据结构?
jdk7 数组+单链表
jdk8 数组+单链表+红黑树
2.传统HashMap的缺点(为什么引入红黑树?)
JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。
在jdk1.8版本后,java对HashMap做了改进,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度。
红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。加快检索速率。
3.HashMap 是如何扩容的?
当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n
ps:
容量(Capacity):是指 HashMap 中桶的数量,默认的初始值为 16。
负载因子(LoadFactor)):也被称为装载因子,默认值为 0.75f
4.HashMap为什么二倍扩容?
HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低!
5.HashMap与ConcurrentHashMap扩容时,有啥区别?
HashMap 是直接在老数据上面进行扩容,多线程环境下,会有线程安全的问题,而 ConcurrentHashMap 就不太一样,扩容过程是这样的:
从数组的队尾开始拷贝;
拷贝数组的槽点时,先把原数组槽点锁住,拷贝成功到新数组时,把原数组槽点赋值为转移节点;
从数组的尾部拷贝到头部,每拷贝成功一次,就把原数组的槽点设置成转移节点;
直到所有数组数据都拷贝到新数组时,直接把新数组整个赋值给数组容器,拷贝完成。
简单来说,通过扩容时给槽点加锁,和发现槽点正在扩容就等待的策略,保证了 ConcurrentHashMap 可以慢慢一个一个槽点的转移,保证了扩容时的线程安全,转移节点比较重要
6.HashMap中链表何时转化为红黑树?
在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)
7.HashMap插入数据方式?
JDK 7 使用的是头插法(先将原位置的数据移到后 1 位,再插入数据到该位置)
JDK 8 使用的是尾插法(直接插入到链表尾部/红黑树)
8.为什么提倡数组大小是 2 的幂次方?
因为只有大小是 2 的幂次方时,才能使 hash 值 % n(数组大小) == (n-1) & hash 公式成立。