vlambda博客
学习文章列表

【Java】为什么1.8中HashMap链表转换成红黑树的阈值是8,红黑树转链表的阈值是6?

通过本文,可以学到如下知识点:
① 全面地了解为什么HashMap链表转红黑树的阈值是8?
② 为什么红黑树还原成链表的阈值是6?

一、为什么链表转换成红黑树的阈值是8?

红黑树的插入、删除、查询的最坏时间复杂度都是O(logN)的,因此在hash函数处于极端条件下(散列效果非常不好,生成的哈希值非常不均匀),性能下降的程度是可以接受的。

由于红黑树节点TreeNode的大小是常规链表节点Node的两倍,所以只有桶中包含足够多的元素时(说明哈希冲突比较多),我们才会使用树。那为什么这个转换阈值是8呢?

在HashMap的源码中有这样一段注释:

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

解释一下上面这段的意思。理想情况下,在随机hashcode下,一个桶中的节点出现的个数服从参数λ=0.5的泊松分布,文中给出了桶长度k的概率表。
由表可以看出,桶的长度为8的概率都已经非常小了。

同时,红黑树的平均查找长度是O(logN),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成红黑树的必要。

所以JDK作者综合以上因素选择了8作为阀值。

二、为什么红黑树转换成链表的阈值是6?

当桶中元素个数小于等于6时,红黑树节点又会还原成链表节点。这个阈值为什么是6呢?这个值也是一个trade-off之后的值。

链表长度如果是小于等于6,平均查找长度是 6/2=3,虽然速度也挺快的,但是插入和移除元素时转化为树结构和平衡树的时间并不会太短。

中间有个差值7可以防止链表和树之间频繁的转换。想象一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。