HashMap源码
前言:看了很多网上现有的源码解析,不是很详细,就动手做了一份详细到每行都带注释的源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; // 哈希表数组
Node<K,V> p; // 当前节点
int n, i; // 数组长度和索引
// 如果哈希表数组为空或长度为0,则进行扩容操作,并将新的数组赋值给tab变量
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 计算要插入的节点在数组中的索引位置i,并获取该位置处的节点p
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; // 用于记录相同键值的已存在节点
K k; // 用于存储节点的键值
// 如果头结点p的哈希值和键值与要插入的哈希值和键值相等,则将头结点作为待插入节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果头结点p是树节点,则调用树节点的putTreeVal方法,在树中执行插入操作
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果头结点p不是树节点,则遍历链表,查找是否已存在相同键值的节点
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 如果链表遍历到末尾仍未找到相同键值的节点,则将新节点加入链表末尾
p.next = newNode(hash, key, value, null);
// 如果链表长度达到树化阈值,则将链表转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1是为了排除第一个节点
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果找到了相同键值的节点e,则更新该节点的值
if (e != null) { // 键值对已存在
V oldValue = e.value;
// 如果onlyIfAbsent标志为false,或者旧值oldValue为null,则进行更新
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e); // 对节点进行访问后的处理
return oldValue; // 返回旧值
}
}
++modCount; // 修改次数+1
if (++size > threshold) // 如果元素数量超过了阈值,则进行数组扩容操作
resize();
afterNodeInsertion(evict); // 插入节点后的处理
return null; // 返回null,表示插入操作完成
}
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; // 返回旧数组
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 扩容阈值翻倍
} else if (oldThr > 0) // 如果旧数组为空但扩容阈值不为0
newCap = oldThr; // 将扩容阈值作为新数组的容量
else { // 旧数组为空且扩容阈值为0,表示使用默认值
newCap = DEFAULT_INITIAL_CAPACITY; // 设置默认的初始容量
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 计算默认的扩容阈值
}
if (newThr == 0) { // 如果新的扩容阈值为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; // 更新table引用指向新的数组
if (oldTab != null) { // 如果旧数组不为空
for (int j = 0; j < oldCap; ++j) { // 遍历旧数组
Node<K, V> e;
if ((e = oldTab[j]) != null) { // 如果旧数组位置上有节点
oldTab[j] = null; // 清空旧数组该位置的节点引用
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 { // 如果该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) { // 根据节点的hash值判断是低位还是高位链表
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; // 将低位链表尾节点的next指针置为null
newTab[j] = loHead; // 将低位链表头节点放入新数组对应位置
}
if (hiTail != null) { // 如果存在高位链表
hiTail.next = null; // 将高位链表尾节点的next指针置为null
newTab[j + oldCap] = hiHead; // 将高位链表头节点放入新数组对应位置
}
}
}
}
}
return newTab; // 返回新的数组
}
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null; // 用于存储键的类类型
boolean searched = false; // 是否已经进行过查找
TreeNode<K,V> root = (parent != null) ? root() : this; // 确定根节点
for (TreeNode<K,V> p = root;;) { // 循环遍历树节点
int dir, ph; K pk;
if ((ph = p.hash) > h) // 当前节点哈希值大于要插入节点的哈希值
dir = -1; // 需要向左子树查找
else if (ph < h) // 当前节点哈希值小于要插入节点的哈希值
dir = 1; // 需要向右子树查找
else if ((pk = p.key) == k || (k != null && k.equals(pk))) // 节点的键与要插入节点的键相等
return p; // 返回当前节点
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
// 执行到这里表示要插入的键与当前节点的键不能直接比较大小,需要进行额外的比较
if (!searched) { // 如果尚未进行过查找
TreeNode<K,V> q, ch;
searched = true; // 设置已进行查找标记
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) || // 在左子树中查找
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null)) // 在右子树中查找
return q; // 返回查找到的节点
}
// 执行到这里表示在子树中没有找到与要插入节点相同的键,需要通过比较确定插入的方向
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p; // 记录父节点
if ((p = (dir <= 0) ? p.left : p.right) == null) { // 到达插入位置
Node<K,V> xpn = xp.next; // 父节点的后继节点
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn); // 创建新的树节点
if (dir <= 0)
xp.left = x; // 插入到父节点的左子树
else
xp.right = x; // 插入到父节点的右子树
xp.next = x; // 更新父节点的后继节点
x.parent = x.prev = xp; // 设置新节点的父节点和前驱节点
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x; // 更新后继节点
moveRootToFront(tab, balanceInsertion(root, x)); // 调整树结构以保持平衡
return null; // 返回null表示插入成功
}
}
}
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; // 定义变量n、index
Node<K,V> e; // 定义变量e
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); // 如果tab为空或者长度小于最小树化容量,则调用resize()方法进行扩容
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 如果tab不为空且对应索引位置上的节点e不为null
TreeNode<K,V> hd = null, tl = null; // 定义变量hd和tl,初始值为null
do {
// 创建替换节点p作为e节点的替代节点,第二个参数为null
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p; // 如果tl为null,将p赋值给hd作为头节点
else {
p.prev = tl; // 否则,将p的prev引用设置为tl
tl.next = p; // 将tl的next引用设置为p
}
tl = p; // 更新tl为p
} while ((e = e.next) != null); // 循环直到遍历完e的所有后继节点
if ((tab[index] = hd) != null)
hd.treeify(tab); // 将头节点hd作为索引位置的新节点,并调用treeify()方法将链表转化为树结构
}
}
final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit) {
TreeNode<K, V> b = this; // 当前节点作为树节点
// 用于保存拆分后的低位链表的头和尾节点
TreeNode<K, V> loHead = null, loTail = null;
// 用于保存拆分后的高位链表的头和尾节点
TreeNode<K, V> hiHead = null, hiTail = null;
int lc = 0, hc = 0; // 用于记录低位链表和高位链表的节点数量
// 遍历当前树节点链表
for (TreeNode<K, V> e = b, next; e != null; e = next) {
next = (TreeNode<K, V>) e.next;
e.next = null; // 断开当前节点与下一个节点的连接
// 根据节点的hash值判断应该加入低位链表还是高位链表
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e; // 如果低位链表尾节点为null,当前节点作为低位链表的头节点
else
loTail.next = e; // 否则,将当前节点链接到低位链表尾节点的next引用上
loTail = e; // 更新低位链表尾节点为当前节点
++lc; // 低位链表节点数量加1
} else {
if ((e.prev = hiTail) == null)
hiHead = e; // 如果高位链表尾节点为null,当前节点作为高位链表的头节点
else
hiTail.next = e; // 否则,将当前节点链接到高位链表尾节点的next引用上
hiTail = e; // 更新高位链表尾节点为当前节点
++hc; // 高位链表节点数量加1
}
}
// 处理低位链表
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map); // 如果低位链表节点数量小于等于阈值,将其转化为普通节点并更新到tab对应的位置上
else {
tab[index] = loHead; // 否则,直接将低位链表的头节点设置到tab对应的位置上
if (hiHead != null)
loHead.treeify(tab); // 如果同时存在高位链表,将低位链表转化为树结构
}
}
// 处理高位链表
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map); // 如果高位链表节点数量小于等于阈值,将其转化为普通节点并更新到tab对应的位置上
else {
tab[index + bit] = hiHead; // 否则,直接将高位链表的头节点设置到tab对应的位置上
if (loHead != null)
hiHead.treeify(tab); // 如果同时存在低位链表,将高位链表转化为树结构
}
}
}