get 方法源码如下
java
final Node<K, V> getNode(int hash, Object key) {
Node<K, V>[] tab; // 哈希桶数组引用
Node<K, V> first, e; // first: 桶内首节点; e: 遍历用临时节点
int n; K k; // n: 数组长度; k: 临时存储节点的键
// 1. 检查哈希表状态:数组非空且长度>0,且目标桶存在节点
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 2. 优先检查首节点:哈希值匹配 + 键匹配(引用或 equals)
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k)))) {
return first;
}
// 3. 存在后续节点时,区分链表/红黑树处理
if ((e = first.next) != null) {
// 3.1 红黑树节点:调用树查找方法
if (first instanceof TreeNode) {
return ((TreeNode<K, V>) first).getTreeNode(hash, key);
}
// 3.2 链表节点:遍历查找
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
return e;
}
} while ((e = e.next) != null); // 遍历到链表末尾
}
}
// 4. 未找到节点
return null;
}
get方法通过getNode实现键值对查找,核心是利用哈希定位与数据结构适配实现高效查询,步骤如下:
1.哈希值计算
调用hash(key)生成二次哈希值,确保与插入时的定位逻辑一致。
2.索引定位与节点查找
计算索引后,检查哈希表状态及目标位置:
哈希表为空、长度为 0 或索引位置无节点:返回null;
索引位置首节点键匹配:直接返回该节点;
首节点不匹配时,根据结构处理:
红黑树节点:调用getTreeNode从根节点开始按红黑树规则查找;
链表节点:遍历链表逐一比对,找到匹配节点则返回,否则返回null。
3.效率优化
优先判断首节点(最常访问),避免无效遍历;红黑树将查找复杂度从链表的 O (n) 降至 O (log n),平衡查询效率。
put 方法源码如下
java
// 核心插入方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
// 1. 哈希表未初始化或为空时,进行扩容(初始化)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 2. 计算索引,定位哈希桶,桶为空则直接插入新节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K, V> e;
K k;
// 3. 检查首节点是否匹配(哈希和键都匹配)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 4. 首节点是红黑树节点,调用红黑树插入逻辑
else if (p instanceof TreeNode)
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
// 5. 首节点是链表节点,遍历链表处理
else {
for (int binCount = 0; ; ++binCount) {
// 遍历到链表尾部,插入新节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 链表长度达标,尝试转红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 找到已存在的节点,跳出遍历
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 6. 处理已存在的节点,更新值
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 7. 插入新节点后,检查是否需要扩容、更新计数等
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
put方法的核心是通过putVal实现键值对的插入或更新,整体流程围绕哈希计算、节点定位、结构适配及动态扩容展开,具体步骤如下:
1.哈希值计算
调用hash(key)对键进行二次哈希(高 16 位与低 16 位异或),减少哈希冲突,为后续定位存储位置提供依据。
2.哈希表初始化 / 扩容
若哈希表(table)未初始化(为空或长度为 0),通过resize()方法完成初始化(默认容量 16,负载因子 0.75,阈值 12);若后续元素数量超过阈值,resize()会将容量翻倍(确保为 2 的幂次方),维持高效存储。
3.索引定位与节点插入
利用(n-1) & hash计算存储索引(n为当前容量,位运算等价于取模且更高效),根据索引位置状态处理:
位置为空:直接创建新节点插入;
位置非空:
首节点键匹配(哈希值与键均相等):更新节点值;
首节点为红黑树节点:调用putTreeVal按红黑树规则插入;
首节点为链表节点:遍历链表,找到匹配节点则更新值,否则尾部插入新节点;当链表长度达到树化阈值(8)且容量≥64 时,调用treeifyBin转为红黑树(容量不足 64 时优先扩容)。
4.计数与扩容检查
插入新节点后,modCount(结构修改计数器)自增,size(元素总数)加 1;若size超过阈值,触发resize()扩容,保证哈希表性能。