前言
2-3树
- HashMap在1.8之后使用了红黑树。红黑树是一种自平衡的二叉树,它可以避免二分搜索树在极端情况下蜕变成链表的情况。在学习红黑树之前,首先要了解2-3树
- 2-3树是一种绝对平衡的多叉树,在这颗树中,任意一个节点,它的左右子树的高度树相同的。
- 2-3树分为两种节点,分别是2-节点,和3-节点。其中,2-节点表示节点中保存一个元素,3-节点则表示节点中保存两个元素。
- 如何生成一个2-3树
-
- 向2-3树中插入30和25
- 向2-3树中插入30和25
-
- 当插入39的时候,一个节点就容纳了3个元素了,那么就要进行分裂操作
- 当插入39的时候,一个节点就容纳了3个元素了,那么就要进行分裂操作
-
- 然后再插入20和33,可以正常的容纳这两个插入的元素
- 然后再插入20和33,可以正常的容纳这两个插入的元素
-
- 再继续插入17和43时,有两个节点都出现了容纳3个元素,那么这两个节点都需要进行分裂操作
- 再继续插入17和43时,有两个节点都出现了容纳3个元素,那么这两个节点都需要进行分裂操作
-
- 插入27和35,两个节点都可以容纳这两个新插入的元素
- 插入27和35,两个节点都可以容纳这两个新插入的元素
-
- 那么再最后插入22,结果发现,一个节点容纳了3个元素,要进行分裂,但是分裂后,叶子节点的高度不一致,那么就要再进行聚合操作
- 那么再最后插入22,结果发现,一个节点容纳了3个元素,要进行分裂,但是分裂后,叶子节点的高度不一致,那么就要再进行聚合操作
- 在了解完2-3树之后,回过头来看一下红黑树,也就是说,2-3树怎么转变成红黑树?方式很多,此处可以采用左倾红黑树的方式,来将2-3转换为红黑树,转换规则如下:
如上规则进行转换上面的2-3树时,构造的红黑树如下
红黑树
红黑树必须满足以下5个条件:
- 每个节点要么是红色,要么是黑色
- 根节点一定是黑色
- 每个叶子节点一定是黑色
- 如果一个节点上红色,那么它的左右子节点一定都是黑色的
- 从任意一个节点到叶子节点,所经过的黑色节点的数量一样多
也许你会发现上面的2-3树转换成红黑树之后33节点是红色的,这并不满足所有叶子节点都是黑色这一条件
其实,那是因为没有画上空的叶子节点,完整的红黑树如下
HashMap源码解析
当我们使用HashMap的时候,首先会通过HashMap的构造方法创建HashMap,然后通过put方法向HashMap对象赋值
HashMap的构造函数
HashMap的构造方法
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
上面代码做的操作是给loadFactor赋值
loadFactor是什么?它是HashMap的加载因子,也就是说,元素所占的空间达到加载因子的规定值的时候,那么就会执行扩容
DEFAULT_LOAD_FACTOR的值为0.75f
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
0.75的含义就是:如果数组中存储的元素长度达到了原长度的75%,那么就需要执行扩容操作
put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put方法里面只是调用了putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0) {
//如果是空的table,那么默认初始化一个长度为16的Node数组
n = (tab = resize()).length;
}
if ((p = tab[i = (n - 1) & hash]) == null) {
//如果计算后的下标i,在tab数组中没有数据,那么则新增Node节点
tab[i] = newNode(hash, key, value, null);
//判断是否有哈希冲突,如果没有,直接向数组赋值
} else {
Node<K, V> e;
K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {
//如果与已存在的Node是相同的key值
e = p;
}
else if (p instanceof TreeNode) {
//如果与已存在的Node是相同的key值,并且是树节点
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
} else {
//如果与已存在的Node是相同的key值,并且是普通节点,则循环遍历链式Node,并对比hash和key,如果都不相同,则将新的Node拼装到链表的末尾。如果相同,则进行更新
for (int binCount = 0; ; ++binCount) {
//获得p节点的后置节点,赋值给e。直到遍历到横向链表的最后一个节点,即:该节点的next后置指针为null
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// binCount从0开始,横向链表中第2个node对应binCount=0,如果Node链表大于8个Node,那么试图变为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) {
treeifyBin(tab, hash);
}
break