红黑树系列:
一、前言
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之前,都还是采用连续数组+链表的方式来存储。