- 介绍下 HashMap 的底层数据结构吧

现在用的都是 JDK 1.8,底层是由“数组+链表+红黑树”组成,如下图,而在 JDK 1.8 之前是由“数组+链表”组成
红黑树作用:主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。
什么时候用链表?什么时候用红黑树?
对于插入,默认使用链表节点。当同一个索引位置的节点在新增后达到
9个(阈值8,阈值为8是在时间和空间上权衡的结果)

红黑树节点大小约为链表节点的2倍,在节点太少时,红黑树的查找性能优势并不明显
但是:转回链表节点是用的6

HashMap 有哪些重要属性?分别用于做什么的?

  1. size
  2. threshold:扩容阈值,当 HashMap 的个数达到该值,触发扩容。(在我们新建 HashMap 对象时, threshold 还会被用来存初始化时的容量。HashMap 直到我们第一次插入节点时,才会对 table 进行初始化,避免不必要的空间浪费。)
  3. loadFactor:负载因子,扩容阈值 = 容量 * 负载因子。 负载因子默认值是0.75

HashMap 的默认初始容量是多少? 16 (why:6是2的N次方,并且是一个较合理的大小。如果用8或32,也是OK的,主要根据使用情况)
HashMap 的容量有什么限制吗?容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方
容量在hashmap内部怎么算的

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

|=(或等于) 两个二进制数按位取或
->>>(无符号右移)
cap - 1为了处理 cap 本身就是 2 的N次方的情况
计算机底层是二进制的,移位和或运算是非常快的,所以这个方法的效率很高

HashMap 的容量必须是 2 的 N 次方,这是为什么?

计算索引位置的公式为:(n - 1) & hash,当 n 为 2 的 N 次方时,n - 1 为低位全是 1 的值,此时任何值跟 n - 1 进行 & 运算的结果为该值的低 N 位,达到了和取模同样的效果,实现了均匀分布。当 n 不为 2 的 N 次方时,hash 冲突的概率明显增大。

java hashmap红黑树_数组


JDK 1.8 主要进行了哪些优化

  1. 底层数据结构从“数组+链表”改成“数组+链表+红黑树”,主要是优化了 hash 冲突较严重时,链表过长的查找性能:O(n) -> O(logn)。
  2. 计算 table 初始容量的方式发生了改变,老的方式是从1开始不断向左进行移位运算,直到找到大于等于入参容量的值;新的方式则是通过**“5个移位+或等于运算**”来计算
  3. 优化了 hash 值的计算方式,新的只是简单的让高16位参与了运算
  4. 扩容时插入方式从“头插法”改成“尾插法”,避免了并发下的死循环
  5. 扩容时计算节点在新表的索引位置方式从“h & (length-1)”改成“hash & oldCap”,性能可能提升不大,但设计更巧妙、更优雅。
    扩容时计算节点在新表的索引位置方式从“h & (length-1)”改成“hash & oldCap”,性能可能提升不大,但设计更巧妙、更优雅。

java hashmap红黑树_数组_02