深入理解JDK8中的HashMap

  一、首先看一下HashMap的数据结构(数组+链表/红黑树),如下图:

 

  1、红黑树特性(缺一不可):

(1)、每个节点要么是红色要么是黑色。

(2)、根节点是黑色。

(3)、所有叶子节点都是黑色(叶子节点为NIL或者NULL节点)。

(4)、不存在两个连续的红色节点。

(5)、任意节点(包含跟节点)到其叶子节点的所有路径都包含相同数目的黑色节点。

  2、为什么HashMap中使用红黑树而不使用AVL树呢?

红黑树被称为弱AVL树,牺牲了严格的高度平衡的优越条件为代价(红黑树左右子树的高度差不超过一倍即可)使其能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作;此外,由于它的设计,任何不平衡都会在三次旋转之内解决。因为HashMap的使用场景中插入和删除操作是非常频繁的,所以在HashMap中使用了红黑树。

  3、红黑树RBT与平衡二叉树AVL比较:

(1)、红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

(2)、红黑树和AVL树的区别在于它使用颜色来标识节点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡。

(3)、AVL 树比红黑树更加平衡,但AVL树在插入和删除的时候也会存在大量的旋转操作。所以当你的应用涉及到频繁的插入和删除操作,切记放弃AVL树,选择性能更好的红黑树;当然,如果你的应用中涉及的插入和删除操作并不频繁,而是查找操作相对更频繁,那么就优先选择 AVL 树进行实现。

   二、HashMap元素插入过程及一些参数的详解

1、首先,需要了解HashMap源码中几个重要的参数:

DEFAULT_INITIAL_CAPACITY:默认初始化大小

MAXIMUM_CAPACITY:最大容量

DEFAULT_LOAD_FACTOR:默认的负载因子

TREEIFY_THRESHOLD:链表转化为红黑树的阈值(包含)

UNTREEIFY_THRESHOLD:红黑树转化为链表的阈值(包含)

MIN_TREEIFY_CAPACITY:当数组大小小于该值时,不进行链表向红黑树的转化,而是进行扩容

2、HashMap存储元素过程:

 (1)图中刚开始有计算 key 的 hash 值的设计?

拿到 key 的 hashCode,并将 hashCode 的高16位和 hashCode 进行异或(XOR)运算,得到最终的 hash 值。

 (2)为什么要将 hashCode 的高16位参与运算?

主要是为了在 table 的长度较小的时候,让高位也参与运算,并且不会有太大的开销。

(3)为什么链表转红黑树的阈值是8?

我们平时在进行方案设计时,必须考虑的两个很重要的因素是:时间和空间。对于 HashMap 也是同样的道理,简单来说,阈值为8是在时间和空间上权衡的结果。红黑树节点大小约为链表节点的2倍,在节点太少时,红黑树的查找性能优势并不明显,付出2倍空间的代价不值得。理想情况下,使用随机的哈希码,节点分布在 hash 桶中的频率遵循泊松分布,按照泊松分布的公式计算,链表中节点个数为8时的概率为 0.00000006,这个概率足够低了,并且到8个节点时,红黑树的性能优势也会开始展现出来,因此8是一个较合理的数字。

(4)HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?

默认初始容量是16。HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。

 (5)为什么 HashMap 的容量必须是 2 的 N 次方?

计算索引位置的公式为:(n - 1) & hash,当 n 为 2 的 N 次方时,n - 1 为低位全是 1 的值,此时任何值跟 n - 1 进行 & 运算的结果为该值的低 N 位,达到了和取模同样的效果,实现了均匀分布。实际上,这个设计就是基于公式:x mod 2^n = x & (2^n - 1),因为 & 运算比 mod 具有更高的效率。当 n 不为 2 的 N 次方时,hash 冲突的概率明显增大。

(6)为什么HashMap的负载因子默认为0.75?

 在HashMap的类注释上有如图一段解释:大致意思是说负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。

posted on 2021-02-17 21:43  潘大炮  阅读(1767)  评论(0编辑  收藏  举报