HashMap之节点删除
大家一直关注的都是HashMap如何添加节点,当节点数量大于8的时候转化为红黑树,否则使用链表等等,但大家是否有看过删除节点的处理逻辑呢? 今天来看看HashMap删除节点的神来之笔
问题来源
在查看HashMap源码时,有个以下字段,在删除的时候,判断节点数量,最多在小于6的时候,会untreeifying(树转化为链表),在点击这个字段时发现,只有在split()方法中使用,但split()方法在resize()方法中使用,与删除节点无关,遂去翻删除节点的代码逻辑
节点删除
通过remove方法,一路进来,找到删除节点的地方。如下图:
我们进入removeTreeNode方法中,代码如下:
查看方法注释,里面介绍到:如果节点太少,就会把当前bin转换成普通bin。不理解注释没关系,我们进入这个方法中唯一有转化的地方。进入untreeify()方法查看下。代码如下:
噢,第一行看一下,if,else看一下,嗯,这个方法就是获取返回了一个列表(这些方法都是TreeNode类里面的方法,所以请注意,this就是当前要操作的TreeNode节点)。到此明白,这个方法就是把红黑树转化成链表的,那么我的就得分析分析是如何操作的了,而没有使用到定义的全局变量。
删除判断逻辑分析
我们来分析分析上面这个逻辑,进入这个untreeify() 的要求是,root == null, root.right ==null, root.left==null, root.left.left==null四种情况,我们以7个节点的红黑树来分析,A为root节点。
所以进入这个方法主要有以下几种情况,我们一种一种的来分析当满足要求时,节点的个数。(这里默认认为知道红黑树的5个特点,主要是黑平衡)
当这四种情况都满足时,我们可以看出最多节点有如上图所示个数,可以为7个。(大于8个不考虑,因为大于8会变成红黑树)。
1. 最多节点情况:当我们删除节点D时,只满足root.left.left==null这个条件,这棵树仍可以维持红黑树的特点,这时的最大节点数为6.
2. 最少节点情况:当EFG不存在时,在A,B,C,D中删除任意一个节点,都会满足上述四种规则中的一种。则存在最少节点情况,有3个节点。
以上情况都是会将树转化成链表,此时的节点是 3<= nodes <=6 ,由此可以看出,当节点数在小于6时,是可能转化成链表,但不是绝对情况, 所以使用定义的变量(固定数量6)也不正确。只好通过判断去动态获取节点数。
节点数量原因分析
为什么在小于6的时候可能转换成链表,而在大于8的时候转化成红黑树?
主要通过时间查询节点分析,红黑树的平均查询时间为 log(n), 而链表是O(n),平均是O(n)/2。
当节点数为8时,红黑树查询时间2,链表查询时间是:4, 可以看出来当红黑树查询效率大于了链表。(两个函数曲线问题,当节点更多是,比红黑树需要的时间更多)
当节点数为6时,为什么转换成链表,我认为主要时因为节点数太少,如果还是用红黑树,为了维持红黑树的特点,则需要翻转,左旋,右旋,等,更消耗性能。
为什么不是7是转化?
主要是为了给一个过渡,防止频繁转化。 也如上图,7个节点可能正好是一个满二叉树。
在JDK8及以后的版本中,HashMap引入了红黑树结构,其底层的数据结构变成了数组+链表或数组+红黑树。添加元素时,若桶中链表个数超过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(-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
大概的意思是:理想情况下使用随机的哈希码,容器中节点分布在hash桶中的频率遵循泊松分布(具体可以查看http://en.wikipedia.org/wiki/Poisson_distribution),按照泊松分布的计算公式计算出了桶中元素个数和概率的对照表,可以看到链表中元素个数为8时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的。