解析HashMap(三)——红黑树(TreeNode)
一、概述
由于在JDK1.8中,在HashMap
中为了提高性能新加了红黑树,在putVal()
和remove()
方法中都使用到了红黑树。我们来介绍HashMap中几个关键方法。
二、源码分析
1. TreeNode
首先我们要知道,所有有关红黑树的操作都是在HashMap
的内部类TreeNode
中。
TreeNode<K, V>
也是继承Node<K, V>
.
1 | static final class TreeNode<K,V> extends Node<K,V> {...} |
2. 属性:
1 | /* 红黑树中的父节点 */ |
3. 构造函数:
1 | TreeNode(int hash, K key, V val, Node<K,V> next) { |
4. 树化
treeifyBin(Node<K,V>[] tab, int hash)
树化的条件:链表中的个数 ≥ TREEIFY_THRESHOLD
,但是并不是满足这个条件一定树化,当满足这个条件时,进入treeifyBin()
方法,此时会判断两个条件:
- tab为null
- tab的length也就是capacity的大小比MIN_TREEIFY_CAPACITY=64小
因为这个时候认为扩容的效果比树化要好。
树化的三步:
Node
节点变成TreeNode
节点- 链表变成双向链表
- 双向链表变成红黑树
1 | /* 将hash对应的bucket链表红黑树树化 */ |
replacementTreeNode(Node<K,V> p, Node<K,V> next)
1 | // 将Node节点转化成TreeNode节点 |
treeify(Node<K,V>[] tab)
该方法将TreeNode
形成的双向链表转化成红黑树。
1 | /** |
tieBreakOrder(Object a, Object b)
强行比较a
,b
的大小。
1 | /** |
5. 插入
putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v)
在HashMap
的putVal()
方法中,我们使用到了putTreeVal()
方法。
1 | final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v) { |
comparableClassFor(Object x)
1 | /** |
Person类的定义 | 调用 | 返回 |
---|---|---|
class Person |
comparableClassFor(new Person("Tom", 12)) |
null |
class Person implements Comparable |
comparableClassFor(new Person("Tom", 12)) |
null |
class Person implements Comparable<String> |
comparableClassFor(new Person("Tom", 12)) |
null |
class Person implements Comparable<Person> |
comparableClassFor(new Person("Tom", 12)) |
Person |
compareComparables()
1 | /** |
6. 删除
在HsahMap
的removeNode
方法中,如果我们删除的是红黑树上的节点,我们调用了红黑树的两个方法treeNode.getTreeNode(hash, key)
和treeNode.removeTreeNode(this, tab, movable)
.我们现在就来分析这两个方法。
getTreeNode(int h, Object k)
这个方法比较简单,先找到当前节点的root
根节点,然后调用根节点的find()
方法,直到找到目标节点。
1 | final TreeNode<K,V> getTreeNode(int h, Object k) { |
removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable)
删除树节点的大致过程:
- 先在链表中删除
- 判断是否需要链表化
- 最后再进行红黑树删除
1 | final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) { |
7. 链表化:
untreeify(HashMap<K,V> map)
我们在调用rmoveTreeNode()
和扩容时调用split()
时有可能使用该方法。
1 | /** |
replacementNode(Node<K,V> p, Node<K,V> next)
构造一个Node
节点。
1 | Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) { |
8. 扩容时转移节点
split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit)
我们在扩容时,对于链表的情况是将链表分成两个链表,而对于树也类似;我们将树从给定的结点分裂成低位和高位的两棵树,若新树结点太少则转为线性链表。只有`resize`时会调用。
1 | /* |