前一篇:[ConcurrentHashMap 重要方法实现原理和源码解析(一)(juejin.cn/post/757389...)
九、treeifyBin() 方法
核心作用
链表转红黑树或者扩容方法。
核心常量
- MIN_TREEIFY_CAPACITY:触发红黑树转换的最小哈希表容量,默认值 64(容量小于此值时,优先扩容而非转树)
- TreeNode<K,V>:红黑树的节点类型,继承 Node,额外包含红黑树所需的 prev(前驱)、next(后继)、parent(父节点)、left(左子树)、right(右子树)、red(颜色标记)字段
- TreeBin<K,V>:红黑树的「容器节点」,封装 TreeNode 组成的红黑树,负责处理并发场景下的查询、插入、删除等操作(避免直接暴露 TreeNode 导致的并发安全问题)
源码实现
java
private final void treeifyBin(Node<K,V>[] tab, int index) {
// 局部变量:b=目标桶的头节点;n=哈希表容量;sc=扩容控制参数(辅助tryPresize)
Node<K,V> b; int n, sc;
// 1. 校验哈希表非空(未初始化则无需处理)
if (tab != null) {
// 场景 1:哈希表容量 < 最小转树容量(64)→ 优先扩容,不转树
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1); // 扩容为原容量的 2 倍(n<<1 等价于 n*2)
// 场景 2:哈希表容量≥64,且目标桶的头节点是普通链表节点(b.hash≥0)
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
// 2. 锁定目标桶的头节点:桶级锁,保证转换过程中桶内数据不被并发修改
synchronized (b) {
// 3. 二次校验:确保加锁前,头节点未被其他线程修改(如扩容迁移、已转树)
if (tabAt(tab, index) == b) {
// 局部变量:hd=红黑树根节点;tl=红黑树尾节点(构建TreeNode双向链表用)
TreeNode<K,V> hd = null, tl = null;
// 4. 遍历链表,将所有普通 Node 转为 TreeNode
for (Node<K,V> e = b; e != null; e = e.next) {
// 创建 TreeNode:复制原 Node 的 hash、key、value,prev/next 暂为 null
TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null);
// 构建 TreeNode 双向链表(prev 前驱,next 后继)
if ((p.prev = tl) == null)
hd = p; // 第一个节点作为链表头(后续将成为红黑树根)
else
tl.next = p; // 前一个节点的 next 指向当前节点
tl = p; // 尾节点指针后移
}
// 5. 用 TreeBin 包装 TreeNode 双向链表,替换原桶的头节点
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
十、putAll() 方法
实现说明
先扩容,再循环执行 put() 方法。
源码实现
java
public void putAll(Map<? extends K, ? extends V> m) {
tryPresize(m.size());
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
putVal(e.getKey(), e.getValue(), false);
}
十一、remove() 方法
公开 remove 方法
直接调用 replaceNode,传入 value=null、cv=null(无条件删除)。
源码实现
java
public V remove(Object key) {
return replaceNode(key, null, null);
}
十二、replaceNode() 方法
核心作用
底层核心实现:支持删除、替换、条件替换。
实现说明
- 二次哈希 :和
putVal、get方法一致,减少哈希碰撞 - 死循环重试:应对并发场景(初始化、扩容、CAS 失败),直到操作完成
- 三个场景处理 :
- 场景1:哈希表未初始化 或 目标桶为空 → 无对应 key,直接跳出循环返回 null
- 场景2 :目标桶头节点是扩容标记(
MOVED=-1)→ 协助扩容后重试 - 场景3 :目标桶有节点(非空、非扩容)→ 桶级加锁执行操作
- 子场景3.1 :头节点是链表节点(
fh≥0)- 遍历链表查找目标节点
- 匹配 key:hash 相等 + key 地址/内容相等
- 匹配条件 cv:cv 为 null(无条件)或 cv 与当前 value 相等
- 执行操作:value 非空则替换,为空则删除
- 子场景3.2 :头节点是红黑树容器(TreeBin)
- 查找红黑树中的目标节点(
findTreeNode方法遍历红黑树) - 匹配 cv 条件
- 执行操作:替换 value 或 删除节点
- 红黑树节点数 ≤ 6,回退为链表(
untreeify方法)
- 查找红黑树中的目标节点(
- 子场景3.1 :头节点是链表节点(
- 后续处理:操作完成后,若仅删除操作需要更新计数(减少 1)
源码实现
java
final V replaceNode(Object key, V value, Object cv) {
// 1. 二次哈希:和putVal、get方法一致,减少哈希碰撞
int hash = spread(key.hashCode());
// 2. 死循环重试:应对并发场景(初始化、扩容、CAS失败),直到操作完成
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 场景1:哈希表未初始化 或 目标桶为空 → 无对应key,直接跳出循环返回null
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
// 场景2:目标桶头节点是扩容标记(MOVED=-1)→ 协助扩容后重试
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 场景3:目标桶有节点(非空、非扩容)→ 桶级加锁执行操作
else {
V oldVal = null; // 存储被删除/替换的旧值
boolean validated = false; // 标记是否找到并处理目标节点
// 锁定桶的头节点:仅影响当前桶,不阻塞其他桶操作
synchronized (f) {
// 二次校验:确保加锁前头节点未被修改(如扩容迁移、转树)
if (tabAt(tab, i) == f) {
// 子场景3.1:头节点是链表节点(fh≥0)
if (fh >= 0) {
validated = true;
Node<K,V> pred = null; // 前驱节点(用于删除时调整指针)
// 遍历链表查找目标节点
for (Node<K,V> e = f;;) {
K ek;
// 匹配key:hash相等 + key地址/内容相等
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
V ev = e.val;
// 匹配条件cv:cv为null(无条件)或 cv与当前value相等
if (cv == null || cv == ev || (ev != null && cv.equals(ev))) {
oldVal = ev; // 记录旧值
// 执行操作:value非空则替换,为空则删除
if (value != null)
e.val = value; // 替换value(对应replace方法)
else if (pred != null)
pred.next = e.next; // 非头节点:前驱节点指向当前节点的下一个
else
setTabAt(tab, i, e.next); // 头节点:直接替换桶的头节点为下一个
}
break; // 操作完成,跳出遍历
}
pred = e; // 前驱节点后移
if ((e = e.next) == null)
break; // 遍历结束未找到,跳出
}
}
// 子场景3.2:头节点是红黑树容器(TreeBin)
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
// 查找红黑树中的目标节点(findTreeNode方法遍历红黑树)
if ((r = t.root) != null && (p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
// 匹配cv条件
if (cv == null || cv == pv || (pv != null && cv.equals(pv))) {
oldVal = pv;
// 执行操作:替换value 或 删除节点
if (value != null)
p.val = value; // 替换value
else if (t.removeTreeNode(p)) // 删除红黑树节点,返回是否需要转链表
// 红黑树节点数≤6,回退为链表(untreeify方法)
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
// 操作完成后的后续处理
if (validated) {
if (oldVal != null) {
if (value == null) // 仅删除操作需要更新计数(减少1)
addCount(-1L, -1);
return oldVal; // 返回被删除/替换的旧值
}
break; // 未找到目标节点,跳出循环
}
}
}
return null; // 未找到key,返回null
}
十三、values() 和 entrySet() 方法
ConcurrentHashMap 的 values() 和 entrySet() 方法,均返回基于底层数据的「视图(View)」而非拷贝,核心设计逻辑一致(懒加载 + 缓存复用、弱一致性),但视图类型、支持的操作及用途不同:
- values() :返回
Collection<V>类型视图,仅关注「值」,支持通过视图删除映射,不支持添加 - entrySet() :返回
Set<Map.Entry<K,V>>类型视图,关联「键值对」,支持删除映射和通过Entry.setValue()修改值,不支持添加
两者均避免一次性拷贝所有数据(节省内存 + 实时反映底层变化),适配高并发场景的遍历/修改需求。
源码实现
java
public Set<Map.Entry<K,V>> entrySet() {
EntrySetView<K,V> es;
// values():缓存字段 values(volatile ValuesView<K,V>)
return (es = entrySet) != null ? es : (entrySet = new EntrySetView<K,V>(this));
}
public Collection<V> values() {
ValuesView<K,V> vs;
// entrySet():缓存字段 entrySet(volatile EntrySetView<K,V>)
return (vs = values) != null ? vs : (values = new ValuesView<K,V>(this));
}
十四、toString() 方法
实现说明
- 创建 Traverser 全表遍历器
- 创建 StringBuilder 进行字符串的拼接
源码逐行解析
java
public String toString() {
// 局部变量:t=哈希表数组;f=table长度(未初始化则为0)
Node<K,V>[] t;
int f = (t = table) == null ? 0 : t.length;
// 1. 创建全表遍历器:遍历范围0~f-1(覆盖所有桶)
Traverser<K,V> it = new Traverser<K,V>(t, f, 0, f);
// 2. 初始化字符串构建器(高效拼接字符串,避免String频繁创建)
StringBuilder sb = new StringBuilder();
sb.append('{'); // 起始符
Node<K,V> p;
// 3. 读取第一个有效节点(it.advance()返回下一个节点,null表示遍历结束)
if ((p = it.advance()) != null) {
// 4. 循环遍历所有节点,拼接键值对
for (;;) {
K k = p.key;
V v = p.val;
// 拼接key:若key是当前map实例,替换为"(this Map)"(防循环引用)
sb.append(k == this ? "(this Map)" : k);
sb.append('=');
// 拼接value:若value是当前map实例,替换为"(this Map)"(防循环引用)
sb.append(v == this ? "(this Map)" : v);
// 读取下一个节点,无节点则跳出循环
if ((p = it.advance()) == null)
break;
sb.append(',').append(' '); // 分隔符
}
}
// 拼接结束符,返回最终字符串
return sb.append('}').toString();
}