HashMap 源码阅读(1)
作者:互联网
HashMap 源码阅读(1)
预备知识
二叉树
1. 左子树上,所有节点的值均小于或等于它的根节点的值
2. 右子树上,所有节点的值均大于或等于它的根节点的值
3. 左右子树,符合1和2
红黑树
Red-Black Tree,红黑树,一种自平衡的二叉查找树
- 节点是红色或黑色
- 根节点是黑色
- 所有叶子节点,都是黑色的空节点
- 每个红色节点的两个子节点,都是黑色
- 从任一节点到每个叶子节点的所有路径,都包含相同数目的黑色节点
插入
1)父节点是红色
插入节点为红色,导致两个红色节点,需要调整
特性4:每个红色节点的两个子节点,都是黑色
变色
旋转
-
左旋转
-
右旋转
2)父节点是黑色
插入节点为红色,无需调整
删除
1)删除节点是红色
无需调整
2)删除节点是黑色
删除前驱 & 变色
删除后继 & 变色
HashMap
节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
// getter toString set
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
默认策略
-
默认容量:
DEFAULT_INITIAL_CAPACITY = 1<<4
-
默认加载因子:
DEFAULT_LOAD_FACTOR = 0.75f
-
树化阈值:
TREEIFY_THRESHOLD = 8
-
去树化阈值:
UNTREEIFY_THRESHOLD = 6
-
扩容后容量:2的指数幂
下述newCap在三个地方赋值: 1)newCap = oldCap << 1 2)newCap = oldThr 3)newCap = DEFAULT_INITIAL_CAPACITY 起点为DEFAULT_INITIAL_CAPACITY = 16
if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); }
属性
transient Node<K,V>[] table
transient Set<Map.Entry<K,V>> entrySet
transient int size
transient int modCount
// resize阈值
int threshold
final float loadFactor
null
允许键值均为null,只允许put一个key为null的对象。
key为null的value,取最后一次put的key为null的值。
即:键为null的桶,只有一个节点。其hash为0,占用tab[0]。
resize
resize:size > threshold
说明,默认构造器并分配内存,此时Node[]为null,是在第一次put时,执行resize实现分配
- 扩容时新创建一个数组,将原哈希表赋值给新哈希表,有一定性能消耗:
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]
和if (oldTab != null)
resize
后,Node数组的长度改变,造成hash计算结果改变,原有节点将重新hash(key)
,导致位置发生改变
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 已分配且有数据
if (oldCap > 0) {
// 当前容量已满,不能扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 2倍扩
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 已分配却无数据,计算容量
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 按默认策略分配
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 已分配却无数据,计算resize阈值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 桶,单节点:重hash
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
crud
查
1. 计算给定key的hash
2. table为null,长度为0或定位桶为null
结束
3. 检查桶中第一个节点
是,返回;
4. 检查桶中剩余节点
- 树:调用getTreeNode
- 链表:插到返回,查不到返回null
增|改:modCount
1. table == null
resize
2. 定位
3. 桶为空
插入,结束
4. 检查桶中第一个节点
是,结束
否,继续5
5. 检查桶中剩余节点
- 树:调用putTreeVal
- 链表
1. 不存在,插入,检查是否需要树化
2. 存在,更新
删:modCount
1. table == null
结束
2. 定位
3. 桶为空
结束
4. 检查桶中第一个节点
是,调到6
否,继续5
5. 检查桶中剩余节点
- 树:查找
- 链表:查找
6. 删除,检查是否需要去树化
参考
https://zhuanlan.zhihu.com/p/31805309
清澈。初见 发布了30 篇原创文章 · 获赞 12 · 访问量 9394 私信 关注标签:Node,newCap,HashMap,int,next,源码,阅读,null,节点 来源: https://blog.csdn.net/qq_31854267/article/details/103991856