HashMap系列:树化阀值8,退化阀值6

(一)HashMap系列:负载因子0.75
(二)HashMap系列:树化阀值8,退化阀值6
(三)HashMap系列:2次方扩容
(四)HashMap系列:put元素(不看完将后悔一生!)

红黑树系列:
一、《算法—深入浅出》N叉树的介绍
二、《算法—深入浅出》红黑树的旋转
三、《算法—深入浅出》红黑树的插入
四、《算法—深入浅出》红黑树的删除

一、前言

HashMap源码中,有非常多的知识点可学习,上一篇我们已经学过了负载因子为 0.75 的来由,而本篇,我们将关注两个数字:8与6,分别代表着链表转为红黑树,以及红黑树退化为链表。

二、HashMap与JDK1.7 & JDK1.8

在JDK1.7中,HashMap的数据结构还是连续数组+单链表;
在JDK1.8中,就变为了连续数组+单链表<->红黑树的结构。

红黑树的原理、算法及实现,我已经在之前有写过,大家可以直接去查看。

这里主要讲的是JDK1.8对HashMap的一点小改进:

  • 单链表的查询、插入与删除的时间复杂度都是线性的,即O(n);
  • 红黑树的查询、插入与删除的时间复杂度是O(logN);

很明显,当 hash 冲突太多时,若还用链表,则 HashMap 的性能是不断降低的,但红黑树则很好的平衡了这点。

三、8与6

3.1、链表转为红黑树(Treeify = 8,树化)

源码中,当链表中的元素个数等于8时,则链表将会转化为红黑树。

* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins.  In
* usages with well-distributed user hashCodes, tree bins are
* rarely used.  Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)). The first values are:
*
* 0:    0.60653066
* 1:    0.30326533
* 2:    0.07581633
* 3:    0.01263606
* 4:    0.00157952
* 5:    0.00015795
* 6:    0.00001316
* 7:    0.00000094
* 8:    0.00000006
* more: less than 1 in ten million

注释中有以下几点我们需要关注:

  • TreeNodes 对象所占内存是 普通Nodes 对象的 2倍;
  • 理想情况下,随机哈希码和负载因子为0.75的情况下,桶中个数出现的频率服从泊松分布;

泊松分布的公式:P(X=k) = exp(-λ) * pow(λ, k) / k!
当 λ = 0.5 时,算出的概率如上!链表长度达到8的概率为0.00000006,再之后则为千万分之一!

因此,当链表元素个数为8时,就将链表转化为红黑树。

3.2、红黑树退化为链表(UnTreeify = 6)

  • 如果不设退化阀值,只以8来树化与退化:
    那么8将成为一个临界值,时而树化,时而退化,此时会非常影响性能,因此,我们需要一个比8小的退化阀值;

  • UNTREEIFY_THRESHOLD = 7
    同样,与上面的情况没有好多少,仅相差1个元素,仍旧会在链表与树之间反复转化;

  • 那为什么是6呢?
    源码中也说了,考虑到内存(树节点比普通节点内存大2倍,以及避免反复转化),所以,退化阀值最多为6。

同时,我们还需要注意一点:

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 */
static final int MIN_TREEIFY_CAPACITY = 64;

上述注释说明了,开启树化至少是HashMap元素已经是64个时,才考虑将链表转为红黑树。

而红黑树初始默认大小是16,因此,在扩容至64之前,都还是采用连续数组+链表的方式来存储。

你可能感兴趣的:(HashMap系列:树化阀值8,退化阀值6)