概述
目录
- get方法
- remove 方法
get方法
前面几章我们讲解了,put的源码以及里面的扩容机制,现在我们来讲解get方法,代码相对比较容易理解:
public V get(Object key) {
// tab 引用 map.table
// e 当前元素
// p 目标节点
// n table数组长度
// eh 当前元素hash
// ek 当前元素key
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 扰动运算后得到
更散列的hash值
int h = spread(key.hashCode());
// 条件一:(tab = table) != null
//
true-> 表示已经put过数据
false->表示创建完map后,并没有put
// 条件二:(n = tab.length) > 0
// 条件三:(e = tabAt(tab, (n - 1) & h)) != null
//
true-> 当前key 寻址的桶位 有值
//
false->当前key 寻址的桶位是null,直接返回
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//前置条件:当前有数据
//对比头节点hash与查询key的hash是否一致
// 条件成立:什么头节点与查询key完全一致
if ((eh = e.hash) == h) {
//完全比对 key
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 条件成立:
// -1
fwd 说明当前table正在扩容,且当前查询的这个桶位的数据 已经被迁移走了
// -2
TreeBin节点,需要使用TreeBin 提供的find 方法查询
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 当前桶位已经形成链表的这种情况
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
FWD节点 ForwardingNode类中的find方法,(重写了Node的find):
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
outer: for (Node<K,V>[] tab = nextTable;;) {
// n 表示为扩容创建的 新表的长度
// e 表示在扩容而创建新表使用 寻址算法 得到的 桶位头节点
Node<K,V> e; int n;
// 条件一:永远不成立
// 条件二:永远不成立
// 条件三:永远不成立
// 条件四:在新扩容的表中 重新定位hash对应的头节点
//
true-> 1.在oldTable中 对应的桶位在迁移之前就是null
//
2.扩容完成后,有其它写线程,将此桶位设置为了null
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
//前置条件:扩容后的表 对应hash的桶位一定不是null,e为此桶位的头节点
// e可能为哪些node类型?
// 1.node类型
// 2.TreeBin类型
// 3.FWD类型
for (;;) {
// eh 新扩容后指定桶位的当前节点的hash
// ek 新扩容后指定桶位的当前节点的key
int eh; K ek;
// 条件成立:说明新扩容后的表,当前命中的桶位就是要查询的数据
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
// eh < 0
// -1
fwd
-2
TreeBin节点
// 两种情况:1.TreeBin类型, 2.FWD类型 (新扩容的表,在并发的情况下,可能在此方法
再次拿到FWD类型)
if (eh < 0) {
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
else
// 说明桶位为TreeBin节点,红黑树的查询
return e.find(h, k);
}
// 当前桶位的头节点没有命中,一定是链表
// 1.将当前元素指向链表的下一个元素
// 2.判断是否为空,如果为null,返回null
if ((e = e.next) == null)
return null;
}
}
}
remove 方法
remove 本质上是一种替换为null 的操作,所以里面使用的方法是replaceNode
remove方法中,调用replaceNode方法,传入的第一个null 代表要把key对应的值替换为value值,第二个null,代表key对应值的期望值(如果为空就不判断,不为空,就需要比对value值)
public V remove(Object key) {
return replaceNode(key, null, null);
}
final V replaceNode(Object key, V value, Object cv) {
// 计算 key 经过扰动运算后的hash
int hash = spread(key.hashCode());
// 自旋
for (Node<K,V>[] tab = table;;) {
// f 代表桶位头节点
// n 表示当前table 数组长度
// i 表示hash命中桶位下标
// fh 表示桶位头节点 hash
Node<K,V> f; int n, i, fh;
//CASE1:
// 条件一:tab == null
true-> 表示当前map.table未初始化, false-> 已经初始化
// 条件二:(n = tab.length) == 0
true-> 表示当前map.table未初始化, false-> 已经初始化
// 条件三: (f = tabAt(tab, i = (n - 1) & hash)) == null true-> 命中中桶位为null,break退出
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
//CASE2:
// 前置条件:当前桶位不为null
//
true->FWD节点, 说明当前table正在扩容中,当前是个写操作,所以需要协助table完成扩容,
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//CASE3:
// 前置条件:当前桶位不为null
// 说明当前桶位可能是链表 也可能是 红黑树TreeBin
else {
// oldVal: 保留替换之前的数据引用
V oldVal = null;
// validated: 校验标记
boolean validated = false;
// 加锁当前桶位 头节点
synchronized (f) {
// 判断 锁的对象对不对,防止其他线程在加锁前修改了数据
if (tabAt(tab, i) == f) {
// 条件成立:说明桶位 为链表 或者单个node
if (fh >= 0) {
validated = true;
//e 表示当前循环处理元素
//pred 表示当前循环节点的上一个节点
for (Node<K,V> e = f, pred = null;;) {
// 当前节点key
K ek;
// 条件一:e.hash == hash true->说明当前节点的hash与查找节点hash一致
// 条件二:(ek = e.key) == key || (ek != null && key.equals(ek))
//
true-> 说明key 与查询的key完全一致
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
// 当前节点的value
V ev = e.val;
// 条件一:cv == null true-> 替换的值为null 那么就是一个删除操作
// 条件二:cv == ev || (ev != null && cv.equals(ev))
那么是一个替换操作
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
// 删除 或 替换
// 将当前节点的值 赋给 oldVal 后续返回用到
oldVal = ev;
//条件成立:说明当前是一个替换操作
// value 是传进来的替换值,如果没有替换值就是删除操作
if (value != null)
// 直接替换
e.val = value;
// 条件成立:删除操作
并且删除的不是头节点
else if (pred != null)
// 当前节点的上一个节点,指向当前节点的下一个节点
pred.next = e.next;
// 条件成立:删除操作
删除的是头节点
else
// 说明当前节点即为头节点,只需要将 桶位位置设置为头节点的下一个节点
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
// 条件成立:TreeBin
else if (f instanceof TreeBin) {
validated = true;
// 转换为实际类型 TreeBin t
TreeBin<K,V> t = (TreeBin<K,V>)f;
//r 表示 红黑树 根节点
//p 表示 红黑树中查找到对应key 一致的node
TreeNode<K,V> r, p;
// 条件一:(r = t.root) != null
理论上是成立
// 条件二:TreeNode.findTreeNode 以当前节点为入口,向下查找key(包括本身节点)
//
true->模式查找到相应key 对应的node节点。会赋值给p
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
//保存 p.val 到 pv
V pv = p.val;
// 条件一:cv == null
成立:删除操作
// 条件二:cv == pv || (pv != null && cv.equals(pv))
成立:替换操作
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
// 删除 或 替换
oldVal = pv;
// 替换操作
if (value != null)
p.val = value;
//删除操作
else if (t.removeTreeNode(p))
// untreeify:把红黑树 转换为链表
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
//当其它线程修改过桶位 头节点时,当前线程sync头节点 锁错对象时,validated 为false,会进入下次自旋
//说明操作完了,不需要自旋了,需要break 或者 return
if (validated) {
// 条件成立:找到了 删除或替换 的key-value 了
if (oldVal != null) {
// value == null当前是删除操作
if (value == null)
//计数统计,传-1 是让addCount 方法中不扩容
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
最后
以上就是美满小蜜蜂为你收集整理的ConcurrentHashMap第五讲:get方法,remove方法的全部内容,希望文章能够帮你解决ConcurrentHashMap第五讲:get方法,remove方法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复