基本介绍
跳跃表的性质如下:
- 最底层的数据节点按照关键字
key
升序排列 - 包含多级索引,每个级别的索引节点按照其关联的数据节点的关键字
key
升序排列 - 高级别索引是其低级别索引的子集。
- 如果关键字
key
在级别level=i
的索引中出现,则级别level<=i
的所有索引都包含该key
跳跃表ConcurrentSkipListMap
的数据结构如下图所示,下图一共有三层索引,最底下为数据节点,同一层索引中,索引节点之间使用right
指针相连,上层索引节点的down
指针指向下层的索引节点。
源码分析
核心字段分析
- head 指向 node(BASE_HEADER) 的顶层索引。
/**
* The topmost head index of the skiplist.
*/
private transient volatile HeadIndex<K,V> head;
- BASE_HEADER 头结点,即最底层(
level=1
)索引的头节点的value值
/**
* Special value used to identify base-level header
*/
private static final Object BASE_HEADER = new Object()
- Node 静态内部类,即数据节点
/**
* 数据节点
*/
static final class Node<K,V> {
final K key;//数据节点的key
volatile Object value;//数据节点的value
volatile Node<K,V> next;//指向下一个数据节点
/**
* Creates a new regular node.
*/
Node(K key, Object value, Node<K,V> next) {
this.key = key;
this.value = value;
this.next = next;
}
}
- Index 静态内部类,即普通索引节点
/**
* 普通索引节点
*/
static class Index<K,V> {
final Node<K,V> node;//索引节点指向的数据节点
final Index<K,V> down;//当前索引节点的正下方索引节点
volatile Index<K,V> right;//当前索引节点的右索引节点
/**
* Creates index node with given values.
*/
Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
this.node = node;
this.down = down;
this.right = right;
}
}
- HeadIndex 静态内部类,即当前级别索引的头节点
/**
* 当前级别索引的头节点
*/
static final class HeadIndex<K,V> extends Index<K,V> {
final int level;//所处索引级别
/**
* node:当前索引指向的数据节点
* down:当前索引节点的正下方索引节点
* right:当前索引节点的右索引节点
* level:当前索引头节点所处的索引级别
*/
HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
super(node, down, right);
this.level = level;
}
}
查询
根据指定的key查询节点,源码如下:
public V get(Object key) {
//调用doGet方法
return doGet(key);
}
/**
* 真正实现查询方法
*/
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)
break outer;
b = n;
n = f;
}
}
return null;
}
在上述代码中,outer
处的for
自旋中,首先查看findPredecessor
:查询指定key节点的前驱节点。该方法在下面的好多地方会调用,例如插入元素,删除元素以及删除元素对应的索引时都会调用。
findPredecessor
方法源码如下:
/**
* 作用1:找到key对应节点的前驱节点,不一定的真的前驱节点,也可能是前驱结点的前驱节点
* 作用2:删除无效的索引,即要删除节点时,将节点的索引也删除掉
*/
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
//r为q节点的右指针指向的节点,r为当前比较节点,每次都比较r节点的key跟查找的key的大小关系
for (Index<K,V> q = head, r = q.right, d;;) {
if (r != null) {
Node<K,V> n = r.node;
K k = n.key;
//该节点已经删除,需要删除其对应的索引
if (n.value == null) {
//该节点已经删除,需要删除其对应的索引
if (!q.unlink(r))
break; // restart
r = q.right; // reread r
continue;
}
//当前查找的key比r节点的key大,所以r、q节点都向右移动
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
}
//当q的下方索引节点为空,则说明已经到数据节点层了,需要退出进行后续查找处理
if ((d = q.down) == null)
return q.node;
/**
* 此时当前查找的key小于r节点的key,需要往下一级索引查找
* d节点赋值为为q节点为正下方节点,即下一级索引的正下方节点
*/
q = d;
r = d.right;
}
}
}
findPredecessor
方法的查找过程图示如下:假设要查找节点6
由于当前r
节点的key
比查询的key
小,所以,r、q
节点都向右移动,即执行如下代码:
//当前查找的key比r节点的key大,所以r、q节点都向右移动
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
此时r
节点指向的数据节点为10,10节点的key
比6节点的key大,此时需要执行如下代码:
/**
* 此时当前查找的key小于r节点的key,需要往下一级索引查找
* d节点赋值为为q节点为正下方节点,即下一级索引的正下方节点
*/
q = d;
r = d.right;
此时r
节点指向的数据节点为5,5节点的key
比6节点的key
小,q、r
节点向右移动,如下图所示
此时r
节点指向的数据节点为10,10节点的key
比6节点的key
大,同理需要往下级索引走,如下图所示:
此时r
节点指向的数据节点为10,10节点的key
比6节点的key
大,同理需要往下级索引走,但是此时下一级索引为空了,即(d = q.down) == null
了,此时执行的代码如下, 返回q
索引指向的节点,即返回节点5.
//当q的下方索引节点为空,则说明已经到数据节点层了,需要退出进行后续查找处理
if ((d = q.down) == null)
return q.node;
以上就是方法findPredecessor
的查找流程,咱们接着继续看上面的doGet
方法
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
//n节点为b节点的下一个节点
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
//f节点为n节点的下一个节点
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
//如果发现n节点的value为null,说明n节点已经欸删除了
if ((v = n.value) == null) { // n is deleted
//当前线程帮助删除对应节点
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
//如果n节点key与查找的key一样的话,说明n就是要查找的节点,返回n节点的value值
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)
break outer;
//如果n节点的key小于要查询的key,则b,n,f节点均向下移动一个节点
b = n;
n = f;
}
}
return null;
}
首先初始化b、n、f
三个节点,如下图所示
发现此时
n
节点指向的节点就是要查询的节点,于是执行如下代码:
if ((c = cpr(cmp, key, n.key)) == 0) {
//说明n就是要查找的节点,返回n节点的value值
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
直接返回n
节点的value
值。查询操作完成。
插入
跳跃表的插入操作分以下四种情况:
-
情况1:跳跃表内存在key一致元素,做替换
-
情况2:插入新元素,无须给新元素生成索引节点
-
情况3:插入新元素,需要给新元素生成索引节点,且索引高度 <
maxLevel
-
情况4:插入新元素,需要给新元素生成索引节点,且索引高度 >
maxLevel
源码如下:
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
//真正执行插入操作
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
//z变量最终指向k-v归属的Node节点
Node<K,V> z; // added node
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
//outer循环,处理并发冲突,进行重试...等其他需要重试的情况
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
//下面为让当前插入的key与节点n作比较
if (n != null) {
Object v; int c;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
z = new Node<K,V>(key, value, n);
//通过cas方式插入节点z
if (!b.casNext(n, z))
break; // restart if lost race to append to b
break outer;
}
}
/**
* 下面的代码为判断是否需要给新插入的节点创建索引
*/
int rnd = ThreadLocalRandom.nextSecondarySeed();
/**
* 0x80000001 => 1000 0000 0000 0000 0000 0000 0000 0001,即最高位和最后一位为1,其余全部是0
* 条件:(rnd & 0x80000001) == 0什么时候成立?rnd这个随机数最低位和最高位同时是0的时候,条件成立,概率是1/4
*/
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
/**
* level:表示z(新插入)节点的索引级别
* max:表示当前跳跃表索引最大级别
*/
int level = 1, max;
/**
* 计算出当前z节点到底应该有多少级别索引,算法:rnd从第二位开始,向后延伸,看有多少个1相连
* 举个例子:
* rnd = 0000 0000 0000 0000 0000 0000 0000 0110 计算出来的level => 3
* rnd = 0000 0000 0000 0000 0000 0000 1111 1110 计算出来的level => 8
*/
while (((rnd >>>= 1) & 1) != 0)
++level;
// 计算得到的 level => 3
//idx:最终指向z节点未处理前后关系的索引,即索引还没有加入到当前层级的索引
Index<K,V> idx = null;
//h:指向head,跳跃表的左上角的index
HeadIndex<K,V> h = head;
// 假设 得到的数是:0b 0000 1111 0000 1111 0000 1111 0000 0110,计算得出的 level = 3
// 假设 h.level = 3 ,该条件成立...
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
idx = new Index<K,V>(z, idx, null);
// index-3 ← idx
// ↓
// index-2
// ↓
// index-1
// ↓
// z-node
}
// 假设 得到的数是:0b 0000 1111 0000 1111 0000 1111 0000 1110,计算得出的z节点索引 level = 4
// 假设 h.level = 3,会执行到else,z节点的计算出来的索引level大于当前跳跃表的最大索引级别3
else { // try to grow by one level
// level 值被重置了,重置为 原 maxLevel + 1,比如说 原head->index.level = 3, =》 level = 4
level = max + 1; // hold in array and later pick the one to use
// 创建一个index数组,长度是 level + 1,假设 level is 4, 创建的数组长度为 5.
@SuppressWarnings("unchecked")Index<K,V>[] idxs =
(Index<K,V>[])new Index<?,?>[level+1];
// 看完这个for循环 就清楚了...原来 index[0] 的这个数组slot 并没有 使用...只使用 [1,level] 这些数组slot了。
for (int i = 1; i <= level; ++i)
// index-4 ← idx
// ↓
// index-3
// ↓
// index-2
// ↓
// index-1
// ↓
// z-node
idxs[i] = idx = new Index<K,V>(z, idx, null);
for (;;) {
//跳跃表左上角节点
h = head;
//获取跳跃表原索引最大高度
int oldLevel = h.level;
//这个条件一般不会成立,只有并发情况下才有可能成立,暂不考虑
if (level <= oldLevel) // lost race to add level
break;
// newh 最终会指向 最新的 headIndex,马上会看到要给baseHeader提升索引
HeadIndex<K,V> newh = h;
// oldbase 指向 baseHeader 节点。
Node<K,V> oldbase = h.node;
// 升级 baseHeader 索引,升高一级。,即for只会执行一次
// 让升高的索引的右指针指向上面新插入节点z的最高索引
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
// 执行完for循环之后,baseHeader 索引长这个样子..让升高的索引的右指针指向上面新插入节点z的最高索引
// index-4 → index-4
// ↓ ↓
// index-3 index-3 ← idx
// ↓ ↓
// index-2 index-2
// ↓ ↓
// index-1 index-1
// ↓ ↓
// baseHeader .... z-node
// cas成功后,map.head 字段指向 最新的 headIndex,即上图中 baseHeader 的 index-4 节点。
if (casHead(h, newh)) {
// h 指向最新的 index-4 节点, idx 指向 z-node 的index-3 节点。
// 为什么 idx 要指向 z-node 的 index-3 节点?
// 因为从index-3 ~ index-1 的这些z-node的索引节点 都没有插入到 索引链表...接下来要做的事情就是 将 这些 索引节点 插入链表。
h = newh;
idx = idxs[level = oldLevel];
break;
}
}
}
// idx 指向z-node 最上层的 且 尚未与前驱 index 串联起来的 index。
// 情况1:z-node 计算出来的z-level <= headLevel,假设 headLevel = 3, z-level = 3
// => idx -> index-3
// 情况2:z-node 计算出来的z-level > headLevel,假设 headLevel = 3, z-level = 5,
// 程序接下来会重置z-level = headLevel + 1 => 4,并创建出 高度为 4 的index 索引。
// => idx -> index-3
// insertionLevel 代表 z-node 尚未处理 队列关系的 层级...
// 比如 情况1 时,insertionLevel = 3 z-node索引没有超过之前的最大索引级别
// 情况2 时,insertionLevel = 3 z-node索引超过之前的最大索引级别
// find insertion points and splice in
splice: for (int insertionLevel = level;;) {
// baseHeader 最高层的index level 赋值给j。 情况1 时 是 3,情况2 时 是 4
int j = h.level;
for (Index<K,V> q = h, r = q.right, t = idx;;) {
if (q == null || t == null)
break splice;
if (r != null) {
Node<K,V> n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (n.value == null) {
if (!q.unlink(r))
break;
r = q.right;
continue;
}
//比较r节点的key与新插入节点z的key大小,如果小于的话,则r,q都向右移
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
// 执行到这里,说明r r节点的key比新插入节点z的key要大,即c < 0
if (j == insertionLevel) {
//连接新插入节点z的索引
if (!q.link(r, t))
break; // restart
if (t.node.value == null) {
findNode(key);
break splice;
}
if (--insertionLevel == 0)
break splice;
}
if (--j >= insertionLevel && j < level)
//继续连接新插入节点z-node的下一级索引,t指向z-node的下一级索引
t = t.down;
//q指向整个跳跃表的索引下一级,继续处理
q = q.down;
r = q.right;
}
}
}
return null;
}
首先还是跟查询操作类似,调用findPredecessor
方法先查找到待插入key
的前驱节点,举个例子,例如我们想要插入节点7,如下图所示:
接着跟查询操作一样的步骤如下,直接看图:
此时
r
节点指向数据节点1,节点1的key
小于待插入的节点7的key
,于是节点q、r
同时向右移动。
此时r
节点指向数据节点10,节点10的key
大于待插入节点7的key
,于是往下一层索引继续查找,执行的代码如下:
d = q.down;
q = d;
r = d.right;
后面的操作类似
此时r
节点的key
大于待插入的节点6的key
,但是q
节点的down
指针已为空,此时直接返回q
节点指向的节点5。
接着回到doPut
方法,先来查看outer
循环,如下:
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
//下面为让当前插入的key与节点n作比较
if (n != null) {
Object v; int c;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
//如果n节点的key小于待插入的key,则b,n,f节点均向下移动一个节点
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
//如果待插入的Key与n节点的key的话,直接将数据通过cas方式进行覆盖
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
//创建出z节点,z节点就是待插入的节点
z = new Node<K,V>(key, value, n);
//通过cas方式插入节点z
if (!b.casNext(n, z))
break; // restart if lost race to append to b
break outer;
}
}
首先初始化三个节点b、n、f
,n
节点为b
节点的下一个节点,而f
节点为n
节点的下一个节点,如下图所示
接着比较节点n
与待插入的key
的大小,此时n
节点的key
小于待插入节点的key
,于是b、n、f
三个节点均向下移动如下图所示
此时n
节点的key
大于待插入的key
,此时执行如下代码,通过cas方式修改b
节点的下一个节点为z
节点,接着跳出outer循环。
//此时`n`节点的`key`大于待插入的`key`
//创建出z节点,就是待插入的节点
z = new Node<K,V>(key, value, n);
//通过cas方式插入节点z
if (!b.casNext(n, z))
break; // restart if lost race to append to b
/**
* 修改n节点的而下一个节点为z节点
*/
boolean casNext(Node<K,V> cmp, Node<K,V> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
然后我们知道doPut
剩下的代码无非就是判断是否给新插入的节点z
创建索引,如果需要创建对应的索引。
首先通过int rnd = ThreadLocalRandom.nextSecondarySeed();
计算出一个随机数,接着进行如下判断:
if ((rnd & 0x80000001) == 0) {
//给新插入的节点创建索引
}
如果rnd & 0x80000001) == 0
就给新插入的z
节点创建索引,我们知道0x80000001 = 1 000 0000 0000 0000 0000 0000 0000 0001
即最高位和最后一位为1,其余全部是0,
条件:(rnd & 0x80000001) == 0
什么时候成立?
rnd这个随机数最低位和最高位同时是0的时候,条件成立,概率是1/4
举个例子:例如rnd = 0000 0000 0000 0000 0000 0000 0000 0110 = 3
条件就成立。
如果条件成立的话,接着计算到底给z
节点创建几级索引,代码如下:
/**
* level:表示z(新插入)节点的索引级别
* max:表示当前跳跃表索引最大级别
*/
int level = 1, max;
/**
* 计算出当前z节点到底应该有多少级别索引,算法:rnd从第二位开始,向后延伸,看有多少个1相连
* 举个例子:
* rnd = 0000 0000 0000 0000 0000 0000 0000 0110 计算出来的level => 3
* rnd = 0000 0000 0000 0000 0000 0000 1111 1110 计算出来的level => 8
*/
while (((rnd >>>= 1) & 1) != 0)
++level;
// 计算得到的 level => 3
通过while
条件((rnd >>>= 1) & 1) != 0
满足几次就创建几级索引。例如:
rnd = 0000 0000 0000 0000 0000 0000 0000 0110
计算出来的level => 3rnd = 0000 0000 0000 0000 0000 0000 1111 1110
计算出来的level => 8
然后接着比较计算出来的z
节点的索引跟现有的跳跃表的索引级别大小。
- 情况一:
z
节点计算出来的索引level比跳跃表的level小 - 情况二:
z
节点计算处理的索引level比跳跃表的level大。此时会选择最终的level为原来的调表的level + 1
情况一
给z
节点创建索引的步骤如下图所示,此时z
节点的索引还没有加入跳跃表现有的索引队列中
接着继续执行splice
循环,代码如下:
splice: for (int insertionLevel = level;;) {
// baseHeader 最高层的index level 赋值给j。 情况1 时 是 3,情况2 时 是 4
int j = h.level;
for (Index<K,V> q = h, r = q.right, t = idx;;) {
if (q == null || t == null)
break splice;
if (r != null) {
Node<K,V> n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (n.value == null) {
if (!q.unlink(r))
break;
r = q.right;
continue;
}
//比较r节点的key与新插入节点z的key大小,如果小于的话,则r,q都向右移
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
// 执行到这里,说明r r节点的key比新插入节点z的key要大,即c < 0
if (j == insertionLevel) {
//连接新插入节点z的索引
if (!q.link(r, t))
break; // restart
if (t.node.value == null) {
findNode(key);
break splice;
}
if (--insertionLevel == 0)
break splice;
}
if (--j >= insertionLevel && j < level)
//继续连接新插入节点z-node的下一级索引,t指向z-node的下一级索引
t = t.down;
//q指向整个跳跃表的索引下一级,继续处理
q = q.down;
r = q.right;
}
}
初始化q、r
节点如下图所示
此时r
节点的key
比新插入z
节点,即7节点小,于是两个节点q、t
都向右移动如下图所示
此时r
节点的key
比新插入z
节点,即7节点大,执行如下代码:
//将q节点,t节点为idx指向的节点,r节点连接起来
q.link(r, t)
//继续连接新插入节点z-node的下一级索引,t指向z-node的下一级索引
t = t.down;
//q指向整个跳跃表的索引下一级,继续处理
q = q.down;
r = q.right;
此时r
节点的key
比新插入z
节点,即7节点小,于是两个节点q、t
都向右移动如下图所示
此时r
节点的key
比新插入z
节点,即7节点大,同理,直接看图
情况二
跟情况一类似,这里就不一一画图了
删除
删除方法完成的任务如下:
-
- 设置指定元素value为null
-
- 将指定node从node链表移除
-
- 将指定node的index节点 从 对应的 index 链表移除
public V remove(Object key) {
return doRemove(key, null);
}
/**
* 删除操作
*/
final V doRemove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
//找到key对应节点的前驱节点,不一定的真的前驱节点,也可能是前驱结点的前驱节点
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) < 0)
break outer;
if (c > 0) {
b = n;
n = f;
continue;
}
//找到要删除的key节点了
if (value != null && !value.equals(v))
break outer;
//将节点的值设置为null
if (!n.casValue(v, null))
break;
/**
* n.appendMarker(f):在n节点跟f之间插入一个mark节点,添加mark节点成功,返回true,取反得到false
* b.casNext(n, f):将b节点的next指针指向f节点,这一步成功,n节点就正式出队了,返回true,取反得到false
*/
if (!n.appendMarker(f) || !b.casNext(n, f))
findNode(key); // retry via findNode
else {
//findPredecessor清楚无效的索引,即上面删除的节点的索引
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
}
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
}
return null;
}
同样,首先通过findPredecessor
方法查找到要删除key
的前驱节点,就不一一画图了,直接看找到的前驱节点的图,如下:
接比较n
节点的key
与待删除的key
的大小,此时n
节点的key
小于待删除的key
,即7节点的key
,于是将b、n、f
三个节点都向右移动,如下图:
此时n
节点的key
跟待删除的key
一样,于是执行如下代码:
//将节点的值设置为null
if (!n.casValue(v, null))
break;
/**
* n.appendMarker(f):在n节点跟f之间插入一个mark节点,添加mark节点成功,返回true,取反得到false
* b.casNext(n, f):将b节点的next指针指向f节点,这一步成功,n节点就正式出队了,返回true,取反得到false
*/
if (!n.appendMarker(f) || !b.casNext(n, f))
findNode(key); // retry via findNode
else {
//findPredecessor清楚无效的索引,即上面删除的节点的索引
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
}
/**
* 创建一个mark节点
*/
boolean appendMarker(Node<K,V> f) {
return casNext(f, new Node<K,V>(f));
}
最后再调用findPredecessor
清楚无效的索引,即上面删除的节点的索引。
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
//r为q节点的右指针指向的节点,r为当前比较节点
for (Index<K,V> q = head, r = q.right, d;;) {
if (r != null) {
Node<K,V> n = r.node;
K k = n.key;
//该节点已经删除,需要删除其对应的索引
if (n.value == null) {
//该节点已经删除,需要删除其对应的索引
if (!q.unlink(r))
break; // restart
r = q.right; // reread r
continue;
}
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
}
//d节点赋值为为q节点为正下方节点,即下一级索引的正下方节点
if ((d = q.down) == null)
return q.node;
q = d;
r = d.right;
}
}
}
重点靠如下代码块删除索引的:
//该节点已经删除,需要删除其对应的索引
if (n.value == null) {
//该节点已经删除,需要删除其对应的索引
if (!q.unlink(r))
break; // restart
r = q.right; // reread r
continue;
}
final boolean unlink(Index<K,V> succ) {
return node.value != null && casRight(succ, succ.right);
}
我们知道在上面已经将待删除的7节点的value置为null了,直接看图:
此时r
节点的key
小于待删除节点的key
,于是r、q
节点都向右移动。
此时r,n
节点指向的数据节点的value
值为null
于是执行上面的q.unlink(r)
代码,将q
的右指针指向r
的右指针指向的节点,即就是删除了该level上的7节点的索引节点,如下图所示
此时r
节点的key
大于待删除节点的key
,于是往下一索引走,如下图所示
此时r
节点的key
小于待删除节点的key
,于是r、q
节点都向右移动。
此时r,n
节点指向的数据节点的value
值为null
于是执行上面的q.unlink(r)
代码,将q
的右指针指向r
的右指针指向的节点,即就是删除了该level上的7节点的索引节点,如下图所示
后续操作同理,最终将7节点的索引一一删除完,最终的图下所示