Basic introduction
Skip lists have the following properties:
- The lowest level data node is based on the keyword
key
Ascending order - Contains multiple levels of index, each level of index node according to the key of its associated data node
key
Ascending order - A high-level index is a subset of its low-level index.
- If the keyword
key
In the levellevel=i
Index, then the levellevel<=i
All indexes of thekey
Jump tableConcurrentSkipListMap
The data structure of index is shown in the figure below. There are three levels of index in the figure below. The lowest level is the data noderight
Pointer connected, upper index nodedown
The pointer points to the underlying index node.
Source code analysis
Core field analysis
- Head points to the top-level index of Node (BASE_HEADER).
/** * The topmost head index of the skiplist. */
private transient volatile HeadIndex<K,V> head;
Copy the code
- BASE_HEADER header, that is, the value of the top-level index header
/** * Special value used to identify base-level header */
private static final Object BASE_HEADER = new Object()
Copy the code
- Node static inner class, namely data Node
/** * Data node */
static final class Node<K.V> {
final K key;// Key of the data node
volatile Object value;// Value of the data node
volatile Node<K,V> next;// point to the next data node
/** * Creates a new regular node. */
Node(K key, Object value, Node<K,V> next) {
this.key = key;
this.value = value;
this.next = next; }}Copy the code
- Index A static inner class, that is, a plain Index node
/** * plain index node */
static class Index<K.V> {
final Node<K,V> node;// The data node to which the index node points
final Index<K,V> down;// The index node directly below the current index node
volatile Index<K,V> right;// The right index of the current index node
/** * 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; }}Copy the code
- HeadIndex Static inner class, which is the head node of the current level index
/** * The head node of the current level index */
static final class HeadIndex<K.V> extends Index<K.V> {
final int level;// Index level
/** * node: indicates the data node to which the current index points * Down: indicates the index node directly below the current index node * Right: indicates the right index node of the current index node * level: indicates the index level of the current index head node */
HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
super(node, down, right);
this.level = level; }}Copy the code
The query
According to the specified key query node, source code is as follows:
public V get(Object key) {
// Call the doGet method
return doGet(key);
}
/** * implement the query method */
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)
breakouter; b = n; n = f; }}return null;
}
Copy the code
In the code above, in the for spin at outer, first look at findPredecessor: query the precursor nodes of the specified key node. This method is called in many places, such as when an element is inserted, when an element is deleted, and when an element’s index is deleted.
FindPredecessor method source code is as follows:
/** * function 1: find the precursor node corresponding to the key, not necessarily the real precursor node, may be the precursor node * function 2: delete invalid index, that is, to delete the node index */
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
//r is the node pointed to by the right pointer of the node q, and r is the current comparison node
for (Index<K,V> q = head, r = q.right, d;;) {
if(r ! =null) {
Node<K,V> n = r.node;
K k = n.key;
// This object has been deleted and its index needs to be deleted
if (n.value == null) {
// This object has been deleted and its index needs to be deleted
if(! q.unlink(r))break; // restart
r = q.right; // reread r
continue;
}
// The current key is larger than the key of r node, so r and Q nodes are moved to the right
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue; }}// If the index node below q is empty, it indicates that it has reached the data node layer and needs to exit for subsequent search processing
if ((d = q.down) == null)
return q.node;
/** * The current key is smaller than the key of r node. * d node is set to q node is the node directly below the next level of index */q = d; r = d.right; }}}Copy the code
The findPredecessor method is depicted as follows: Suppose node 6 is searched
Since the key of the current r node is smaller than the query key, both r and Q nodes move to the right, that is, execute the following code:
// The current key is larger than the key of r node, so r and Q nodes are moved to the right
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
Copy the code
At this point, the data node r points to is 10. The key of 10 is larger than that of 6. In this case, the following code needs to be executed:
/** * The current key is smaller than the key of r node. * d node is set to q node is the node directly below the next level of index */
q = d;
r = d.right;
Copy the code
The key of node 5 is smaller than that of node 6. Nodes Q and R move to the right, as shown in the following figure
At this point, the data node pointed to by r node is 10, and the key of 10 node is larger than that of 6 node. Similarly, it is necessary to go to the lower index, as shown in the following figure:
At this point, the data node pointed to by r node is 10, and the key of the 10 node is larger than that of the 6 node. Similarly, it is necessary to go to the lower-level index, but the lower-level index is empty, that is, (d = q.own) == null. At this point, the following code is executed: return the node pointed by the Q index, that is, return node 5.
// If the index node below q is empty, it indicates that it has reached the data node layer and needs to exit for subsequent search processing
if ((d = q.down) == null)
return q.node;
Copy the code
The above is the search process of method findPredecessor, let’s continue to look at the doGet method above
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
// Node n is the next node of node B
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
//f is the next node of n
Node<K,V> f = n.next;
if(n ! = b.next)// inconsistent read
break;
// If the value of node N is null, the node n is deleted
if ((v = n.value) == null) { // n is deleted
// The current thread helps remove the corresponding node
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
// If the key of n is the same as the key of the search node, the value of n is returned
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)
break outer;
// If the key of node n is smaller than the key to be queried, nodes B,n, and F all move down one nodeb = n; n = f; }}return null;
}
Copy the code
First, initialize nodes B, N, and F, as shown in the following figure
Found at this timen
The node that the node points to is the node to be queried, so execute the following code:
if ((c = cpr(cmp, key, n.key)) == 0) {
// indicates that n is the node to be searched
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
Copy the code
Returns the value of n. The query operation is complete.
insert
Skip table insert operation can be divided into the following four situations:
-
Case 1: There is a key consistent element in the skip table
-
Case 2: Insert a new element without generating an index node for the new element
-
Case 3: When a new element is inserted, an index node with an index height < maxLevel needs to be generated for the new element
-
Case 4: When a new element is inserted, an index node with a height greater than maxLevel needs to be generated for the new element
The source code is as follows:
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
// Do the actual insert
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
// the z variable ends up pointing to the Node where k-v belongs
Node<K,V> z; // added node
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
//outer loop, handle concurrent conflicts, retry... And other situations that require retries
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
// Compare the current key with node 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);
// Insert node Z in CAS mode
if(! b.casNext(n, z))break; // restart if lost race to append to b
breakouter; }}/** * The following code determines whether the newly inserted node needs to be indexed */
int rnd = ThreadLocalRandom.nextSecondarySeed();
/** * 0x80000001 => 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 When the lowest and highest bits of RND are 0 at the same time, the condition is valid and the probability is 1/4 */
if ((rnd & 0x80000001) = =0) { // test highest and lowest bits
/** * level: indicates the index level of the z (newly inserted) node * Max: indicates the maximum index level of the current skip table */
int level = 1, max;
/** * Calculate how many level indexes the current z node should have, using the algorithm: RND starts at the second and extends backwards to see how many 1's are connected * for example: * RND = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1110 The calculated level is > 8 */
while (((rnd >>>= 1) & 1) != 0)
++level;
// Level => 3
//idx: the index that finally points to the z node is not contextually related, that is, the index has not been added to the current level of the index
Index<K,V> idx = null;
//h: points to head, the index in the upper left corner of the skip list
HeadIndex<K,V> h = head;
0b 0000 1111 0000 1111 0000 1111 0000 0110
// If h.level = 3, the condition holds...
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
idx = new Index<K,V>(z, idx, null);
/ / the index - 3 please independence idx
/ / left
// index-2
/ / left
// index-1
/ / left
// z-node
}
0b 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1110
// If h.level = 3, else is executed, and the calculated index level of the z node is greater than the maximum index level of the current skip table 3
else { // try to grow by one level
Head ->index. Level = 3, = "level = 4"
level = max + 1; // hold in array and later pick the one to use
// Create an array with the length of level + 1, assuming level is 4.
@SuppressWarnings("unchecked")Index<K,V>[] idxs =
(Index<K,V>[])newIndex<? ,? >[level+1];
// This is a for loop... The original index[0] array slot does not use... Only the array slots [1,level] are used.
for (int i = 1; i <= level; ++i)
/ / the index - 4 please independence idx
/ / left
// index-3
/ / left
// index-2
/ / left
// index-1
/ / left
// z-node
idxs[i] = idx = new Index<K,V>(z, idx, null);
for (;;) {
// Upper left node of skip list
h = head;
// Get the maximum height of the original skip table index
int oldLevel = h.level;
// This condition is usually not true, but can only be true in concurrent cases
if (level <= oldLevel) // lost race to add level
break;
// Newh will eventually point to the latest headIndex, and you'll soon see that baseHeader will be indexed
HeadIndex<K,V> newh = h;
// OldBase points to the baseHeader node.
Node<K,V> oldbase = h.node;
// Upgrade the baseHeader index by one level. , that is, for is executed only once
// make the right pointer of the raised index point to the highest index of the newly inserted node z above
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
// After executing the for loop, the baseHeader index looks like this.. Make the right pointer of the raised index point to the highest index of the newly inserted node Z above
/ / the index - 4 - index - 4
/ / left left
// index-3 index-3 ← IDx
/ / left left
// index-2 index-2
/ / left left
// index-1 index-1
/ / left left
// baseHeader .... z-node
// After the CAS is successful, the map.head field points to the latest headIndex node, that is, the index-4 node of baseHeader in the preceding figure.
if (casHead(h, newh)) {
// h points to the latest index-4 node, and IDx points to the index-3 node of the Z-node.
// Why does idx point to index-3 of a Z-node?
// The index of the z-node from index-3 to index-1 is not inserted into the index list... The next thing to do is to insert these index nodes into the linked list.
h = newh;
idx = idxs[level = oldLevel];
break; }}}// idx points to the uppermost index of the Z-Node that has not been concatenated with the index of the precursor.
// Case 1: Z-level calculated by z-node <= headLevel, assuming headLevel = 3, z-level = 3
// => idx -> index-3
// Case 2: Z-level > headLevel, assuming headLevel = 3, z-level = 5,
// The program will then reset z-level = headLevel + 1 => 4 and create index with height 4.
// => idx -> index-3
// insertionLevel represents the level of z-Node that has not yet processed queue relationships...
// For example, in case 1, insertionLevel = 3 the z-Node index does not exceed the previous maximum index level
// In case 2, insertionLevel = 3 The z-Node index exceeds the previous maximum index level
// find insertion points and splice in
splice: for (int insertionLevel = level;;) {
// baseHeader assigns the highest index level to j. 3 in case 1, 4 in case 2
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;
}
// Compare the key size of r with that of z. If the key size is smaller than that of z, then r and q move to the right
if (c > 0) {
q = r;
r = r.right;
continue; }}// The key of r r is larger than that of z, i.e. C < 0
if (j == insertionLevel) {
// join the index of newly inserted node 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)
// Continue to connect to the next-level index of the newly inserted z-Node. T points to the next-level index of the z-Node
t = t.down;
//q points to the index level below the entire skip table, and continues processingq = q.down; r = q.right; }}}return null;
}
Copy the code
First of all, similar to the query operation, the findPredecessor method is called to find the precursor nodes to be inserted into the key. For example, we want to insert node 7, as shown in the figure below:
Then the same steps as the query operation are as follows:
At this timer
The node points to data node 1key
The value is smaller than the node 7 to be insertedkey
, so the nodeQ, r,
We’re also moving to the right.
At this point, r node points to data node 10, and the key of node 10 is larger than the key of node 7 to be inserted, so the search continues down the index, and the code is as follows:
d = q.down;
q = d;
r = d.right;
Copy the code
The following operations are similar
In this case, the key of node R is larger than that of node 6 to be inserted, but the down pointer of node Q is null. In this case, node 5 pointed to by node Q is directly returned.
Going back to the doPut method, let’s first look at the outer loop as follows:
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
// Compare the current key with node 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 the key of node n is smaller than the key to be inserted, nodes B, N, and F move down one node
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
// If the Key to be inserted is the same as the Key of node N, data is overwritten in CAS mode
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
}
// Create the z node, which is the node to be inserted
z = new Node<K,V>(key, value, n);
// Insert node Z in CAS mode
if(! b.casNext(n, z))break; // restart if lost race to append to b
breakouter; }}Copy the code
First, initialize three nodes B, n, and F, where n is the next node of B and F is the next node of N, as shown in the figure below
Then, the size of node N is compared with that of the key to be inserted. At this point, the key of node N is smaller than that of the key to be inserted, so nodes B, N, and F move down as shown in the following figure
In this case, the key of node N is larger than the key to be inserted. Execute the following code to change the next node of node B to node Z through CAS, and then break out of the outer loop.
// The 'key' of 'n' is larger than the 'key' to be inserted
// Create the z node, the node to be inserted
z = new Node<K,V>(key, value, n);
// Insert node Z in CAS mode
if(! b.casNext(n, z))break; // restart if lost race to append to b
/** * change n node and next node to z node */
boolean casNext(Node<K,V> cmp, Node<K,V> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
Copy the code
Then we learned that all the rest of doPut’s code was to determine whether or not to index the newly inserted node Z, and if necessary to create the corresponding index.
First by int RND = ThreadLocalRandom. NextSecondarySeed (); Calculate a random number and then make the following judgment:
if ((rnd & 0x80000001) = =0) {
// Create an index for the newly inserted node
}
Copy the code
0x80000001 = 000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
Condition :(RND & 0x80000001) == 0 when set?
When the lowest and highest bits of RND are 0 at the same time, the condition is valid and the probability is 1/4
For example, RND = 0000 0000 0000 0000 0000 0110 = 3.
If the condition is true, then calculate how many levels of index to create for the z node, the code is as follows:
/** * level: indicates the index level of the z (newly inserted) node * Max: indicates the maximum index level of the current skip table */
int level = 1, max;
/** * Calculate how many level indexes the current z node should have, using the algorithm: RND starts at the second and extends backwards to see how many 1's are connected * for example: * RND = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1110 The calculated level is > 8 */
while (((rnd >>>= 1) & 1) != 0)
++level;
// Level => 3
Copy the code
Pass the while condition ((RND >>>= 1) &1)! If = 0, create index levels. Such as:
rnd = 0000 0000 0000 0000 0000 0000 0000 0110
The calculated level is > 3rnd = 0000 0000 0000 0000 0000 0000 1111 1110
The calculated level is > 8
Then it compares the index level of the calculated Z node with that of the existing skip list.
- Case 1:
z
The index level calculated by the node is smaller than the level of the skip list - Situation 2:
z
The index level of the node is larger than the level of the skip table.In this case, the final level is set to level + 1 of the original table
Is a
The steps to create an index for the Z node are shown in the following figure. At this time, the index of the Z node has not been added to the index queue of the jump
Continue with the splice loop as follows:
splice: for (int insertionLevel = level;;) {
// baseHeader assigns the highest index level to j. 3 in case 1, 4 in case 2
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;
}
// Compare the key size of r with that of z. If the key size is smaller than that of z, then r and q move to the right
if (c > 0) {
q = r;
r = r.right;
continue; }}// The key of r r is larger than that of z, i.e. C < 0
if (j == insertionLevel) {
// join the index of newly inserted node 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)
// Continue to connect to the next-level index of the newly inserted z-Node. T points to the next-level index of the z-Node
t = t.down;
//q points to the index level below the entire skip table, and continues processingq = q.down; r = q.right; }}Copy the code
Initialize q and R nodes as shown in the following figure
At this point, the key of r node is smaller than that of newly inserted Z node, namely node 7, so the two nodes Q and T move to the right, as shown in the figure below
At this point, the key of r node is larger than that of newly inserted Z node, i.e., 7 node, and the following code is executed:
// connect q node, t node to the node pointed to by idx, and r node
q.link(r, t)
// Continue to connect to the next-level index of the newly inserted z-Node. T points to the next-level index of the z-Node
t = t.down;
//q points to the index level below the entire skip table, and continues processing
q = q.down;
r = q.right;
Copy the code
At this point, the key of r node is smaller than that of newly inserted Z node, namely node 7, so the two nodes Q and T move to the right, as shown in the figure below
At this point, the key of r node is larger than that of newly inserted Z node, namely 7 node. Similarly, look at the figure directly
Case 2
Just like in case one, I’m not going to draw a picture here
delete
The delete method does the following:
-
- Sets the specified element value to null
-
- Removes the specified node from the node list
-
- Removes the index node of the specified node from the corresponding index list
public V remove(Object key) {
return doRemove(key, null);
}
/** * Delete operation */
final V doRemove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
// Find the precursor of the node corresponding to the key, not necessarily the real precursor, but also the precursor of the precursor
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;
}
// Find the key node to delete
if(value ! =null && !value.equals(v))
break outer;
// Set the node value to null
if(! n.casValue(v,null))
break;
/** * n.appendMarker(f) : insert mark node between n and f, return true, return false * b.casnext (n, f) : Next pointer on node B to node F, this step succeeds, node n is officially out of the queue, return true, return false */
if(! n.appendMarker(f) || ! b.casNext(n, f)) findNode(key);// retry via findNode
else {
Findfindpredecessor clear invalid indexes, that is, indexes of the nodes deleted above
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
}
@SuppressWarnings("unchecked") V vv = (V)v;
returnvv; }}return null;
}
Copy the code
Similarly, first of all, the predecessor nodes to delete key are found by findPredecessor method, which is not a picture, and directly look at the figure of the precursor nodes found, as follows:
Then compare the size of the key of node N and the key to be deleted. At this point, the key of node N is smaller than the key to be deleted, that is, the key of node 7. Then, move nodes B, N, and F to the right, as shown in the figure below:
The key of node n is the same as the key to be deleted, so execute the following code:
// Set the node value to null
if(! n.casValue(v,null))
break;
/** * n.appendMarker(f) : insert mark node between n and f, return true, return false * b.casnext (n, f) : Next pointer on node B to node F, this step succeeds, node n is officially out of the queue, return true, return false */
if(! n.appendMarker(f) || ! b.casNext(n, f)) findNode(key);// retry via findNode
else {
Findfindpredecessor clear invalid indexes, that is, indexes of the nodes deleted above
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
}
/** * Create a mark node */
boolean appendMarker(Node<K,V> f) {
return casNext(f, new Node<K,V>(f));
}
Copy the code
Finally, findPredecessor is called to clear the invalid index, that is, the index of the node deleted above.
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
//r is the node to which the right pointer to q points, and r is the current comparison node
for (Index<K,V> q = head, r = q.right, d;;) {
if(r ! =null) {
Node<K,V> n = r.node;
K k = n.key;
// This object has been deleted and its index needs to be deleted
if (n.value == null) {
// This object has been deleted and its index needs to be deleted
if(! q.unlink(r))break; // restart
r = q.right; // reread r
continue;
}
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue; }}// the d node is assigned to q, which is the node directly below the next-level index
if ((d = q.down) == null)
returnq.node; q = d; r = d.right; }}}Copy the code
Delete index by focusing on the following code block:
// This object has been deleted and its index needs to be deleted
if (n.value == null) {
// This object has been deleted and its index needs to be deleted
if(! q.unlink(r))break; // restart
r = q.right; // reread r
continue;
}
final boolean unlink(Index<K,V> succ) {
returnnode.value ! =null && casRight(succ, succ.right);
}
Copy the code
The value of the 7 nodes to be deleted has been set to null.
At this point, the key of r node is smaller than that of the node to be deleted, so both R and Q nodes move to the right.
At this point, the value of the data node pointed to by nodes R and n is null, so the above q.nlink (r) code is executed, and the right pointer of Q points to the node pointed to by the right pointer of R, that is, the index node of 7 nodes at this level is deleted, as shown in the figure below
At this point, the key of r node is larger than that of the node to be deleted, so it goes to the next index, as shown in the following figure
At this point, the key of r node is smaller than that of the node to be deleted, so both R and Q nodes move to the right.
At this point, the value of the data node pointed to by nodes R and n is null, so the above q.nlink (r) code is executed, and the right pointer of Q points to the node pointed to by the right pointer of R, that is, the index node of 7 nodes at this level is deleted, as shown in the figure below
In the same way, the indexes of the seven nodes are deleted one by one, as shown in the final figure