ope电竞竞猜_ope体育电竞官方网站_ope体育电竞app
ope电竞竞猜

变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞app

admin admin ⋅ 2019-05-21 07:10:53

简介

跳表是一个随机化的数据结构,本质便是一种能够进行二分查找的有序链表

跳表在原有的有序链表上面增加了多级索引,通过索引来完结快速查找。

跳表不仅能进步查找功能,一起也能够进步刺进和删去操作的功能。

存储结构

跳表在原有的有序链表上面增加了多级索引,通过索引来完结快速查找。

源码剖析

首要内部类

内部类跟存储结构结合着来看,大约能预测到代码的安排办法。

// 数据节点,典型的单链表结构
static final cla变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞appss Node {
final K key;
// 留意:这儿value的类型是Object,而不是V
// 在删去元素的时分value会指向当时元素自身
volatile Object value;
volatile Node next;

Node(K key, Object value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}

Node(Node next) {
this.key = null;
this.value = this; // 当时元素自身(marker)
this.next = next;
}
}
// 索引节点,存储着对应的node值,及向下和向右的索引指针
static class Index {
final Node node;
final Index down;
volatile Index right;

Index(Node node, Index down, Index right) {
this.node = node;
this.down = down;
this.right = right;
}
}
// 头索引节点,承继自Index,并扩展一个level字段,用于记载索引的层级
static final class HeadIndex extends Index {
final int level;

HeadIndex(Node node, Index down, Index rig变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞appht, int level) {
super(node, down, right);
this.level = level;
}
}

(1)Node,数据节点,存储数据的节点,典型的单链表结构;

(2)Index,索引节点,存储着对应的node值,及向下和向右的索引指针;

(3)HeadIndex,头索引节点,承继自Index,并扩展一个level字段,用于记载索引的层级;

结构办法


public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
public ConcurrentSkipListMap(Comparator comparator) {
this.comparator = comparator;
initialize();
}
public ConcurrentSkipListMap(Map m) {
this.comparator = null;
initialize();
putAll(m);
}
public ConcurrentSkipListMap(SortedMap m) {
this.comparator = m.comparator();
initialize();
buildFromSorted(m);
}

四个结构办法里边都调用了initialize()这个办法,那么,这个办法里边有什么呢?

private static final Object BASE_HEADER = new Object();
private void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingMap = null;
// Node(K key, Object value, Node next)
// HeadIndex(Node node, Index down, Index right, int level)
head = new HeadIndex(new Node(null, BASE_HEADER, null),
null, null, 1);
}

能够看到,这儿初始化了一些特点,并创立了一个头索引节点,里边存储着一个数据节点,这个数据节点的值是空方针,且它的层级是1。

所以,初始化的时分,跳表中只需一个头索引节点,层级是1,数据节点是一个空方针,down和right都是null。

通过内部类的结构咱们知道,一个头索引指针包括node, down, right三个指针,为了便于了解,咱们把指向node的指针用虚线标明,其它两个用实线标明,也便是虚线不是标明方向的。

增加元素

通过【托付,面试别再问我跳表了!】中的剖析,咱们知道跳表刺进元素的时分会通过抛硬币的办法决议出它需求的层级,然后找到各层链中它地点的方位,最终通过单链表刺进的办法把节点及索引刺进进去来完结的。

那么,ConcurrentSkipList中是这么做的吗?让咱们一起来探个终究:

public V put(K key, V value) {
// 不能存储value为null的元素
// 由于value为null符号该元素被删去(后边会看到)
if (value == null)
throw new NullPointerException();
毛宁的老婆是谁// 调用doPut()办法增加元素
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
// 增加元素后存储在z中
Node z; // added node
// key也不能为null
if (key == null)
throw new NullPointerException();
Comparator cmp = comparator;
// Part I:找到方针节点的方位并刺进
// 这儿的方针节点是数据节点,也便是最底层的那条链
// 自旋
outer: for (;;) {
// 寻觅方针节点之前最近的一个索引对应的数据节点,存储在b中,b=before
// 并把b的下一个数据节点存储在n中,n=next
// 为了便于描绘,我这儿把b叫做当时节点,n叫做下一个节点
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
// 假设下一个节点不为空
// 就拿其key与方针节点的key比较,找到方针节点应该刺进的方位
if (n != null) {
// v=value,存储节点value值
// c=compare,存储两个节点比较的巨细
Object v; int c;
// n的下一个数据节点,也便是b的下一个节点的下一个节点(孙子节点)
Node f = n.next;
// 假设n不为b的下一个节点
// 阐明有其它线程修改了数据,则跳出内层循环
// 也便是回到了外层循环自旋的方位,从头来过
if (n != b.next) // inconsistent read
break;
// 假设n的value值为空,阐明该节点已删去,帮忙删去节点
if ((v = n.value) == null) { // n is deleted
// todo 这儿为啥会帮忙删去?后边讲
n.helpDelete(b, f);
break;
}
// 假设b的值为空或许v等于n,阐明b已被删去
// 这时分n便是marker节点,那b便是被删去的那个
if (b.value == null || v == n) // b is deleted
break;
// 假设方针key与下一个节点的key大
// 阐明方针元素地点的方位还在下一个节点的后边
if ((c = cpr(cmp, key, n.key)) > 0) {
// 就把当时节点往后移一位
// 相同的下一个节点也往后移一位
// 再从头查看新n是否为空,它与方针key的联络
b = n;
n = f;
continue;
}
// 假设比较时发现下一个节点的key与方针key相同
// 阐明链表中自身就存在方针节点
if (c == 0) {
// 则用新值替换旧值,并回来旧值(onlyIfAbsent=false)
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
// 假设替换旧值时失利,阐明其它线程先一步修改了值,从头金娜玹来过
break; // restart if lost race to replace value
}
// 假设c<0,就往下走,也便是找到了方针节点的方位
// else c < 0; fall through
}
// 有两种状况会到这儿
// 一是到链表尾部了,也便是n为null了
// 二是找到了方针节点的方位,也便是上面的c<0
// 新建方针节点,并赋值给z
// 这儿把n作为新节点的next
// 假设到链表尾部了,n为null,这毫无疑问
// 假设c<0,则n的key比方针key大,相妆于在b和n之间刺进方针节点z
z = new Node(key, value, n);
// 原子更新b的下一个节点为方针节点z
if (!b.casNext(n, z))
// 假设更新失利,阐明其它线程先一步修改了值,从头来过
break; // restart if lost race to append to b
// 假设更新成功,跳出自旋状况
break outer;
}
}
// 通过Part I,方针节点现已刺进到有序链表中了
// Part II:随机决议是否需求树立索引及其层次,假设需求则树立自上而下的索引
// 取个随机数
int rnd = ThreadLocalRandom.nextSecondarySeed();
// 0x80000001打开为二进制为10000000000000000000000000000001
// 只需两端是1
// 这儿(rnd & 0x80000001)黄日华割鹿刀国语版 == 0
// 相当于排除了负数(负数最高位是1),排除了奇数(奇数最低位是1)
// 只需最高位最低位都不为1的数跟0x80000001做&操作才会为0
// 也便是正偶数
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
// 默许level为1,也便是只需到这儿了就会至少树立一层索引
int level = 1, max;
// 随机数从最低位的第二位开端,有几个接连的1则level就加几
// 由于最低位肯定是0,正偶数嘛
// 比方,1100110,level就加2
while (((rnd >>>= 1) & 1) != 0)
++level;
// 用于记载方针节点树立的最高的那层索引节点
Index idx = null;
// 取头索引节点(这是最高层的头索引节点)
HeadIndex h = head;
// 假设生成的层数小于等于当时最高层的层级
// 也便是跳表的高度不会超越现有高度
if (level <= (max = h.level)) {
// 从第一层开端树立一条竖直的索引链表
// 这条链表运用down指针衔接起来
// 每个索引节点里边都存储着方针节点这个数据节点
// 最终idx存储的是这条索引链表的最高层节点
for变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞app (int i = 1; i <= level; ++i)
idx = new Index(z, idx, null);
}
else { /第五影院/ try to grow by one level
// 假设新的层数超越了现有跳表的高度
// 则最多只增加一层
// 比方现在只需一层索引,那下一次最多增加到两层索引,增加多了也没有含义
level = max + 1; // hold in array and later pick the one to use
// idxs用于存储方针节点建芳华泪如泉涌立的竖起索引的一切索引节点
// 其实这儿直接运用idx这个最高节点也是能够完结的
// 仅仅用一个数组存储一切节点要便利一些
// 留意,这儿数组0号位是没有运用的
@SuppressWarnings("unchecked")Index[] idxs =
(Index[])new Index[level+1];
// 从第一层开端树立一条竖的索引链表(跟上面相同,仅仅这儿顺便把索引节点放到数组里边了)
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index(z, idx, null);
// 自旋
for (;;) {
// 旧的最高层头索引节点
h = head;
// 旧的最高层级
int oldLevel = h.level;
// 再次查看,假设旧的最高层级现已不比新层级矮了
// 阐明有其它线程先一步修改了值,从头来过
if (level <= oldLevel) // lost race to add level
break;
// 新的最高层头索引节点
HeadIndex newh = h;
// 头节点指向的数据节点
Node oldbase = h.node;
// 超出的部分树立新的头索引节点
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex(oldbase, newh, idxs[j], j)petjust;
// 原子更新头索引节点
if (casHead(h, newh)) {
// h指向新的最高层头索引节点
h = newh;
// 把level赋值为旧的最高层级的
// idx指向的不是最高的索引节点了
// 而是与旧最高层平齐的索引节点
idx = idxs[level = oldLevel];
break;
}
}
}
// 通过上面的进程,有两种状况
// 一是没有超出高度,新建一条方针节点的索引节点链
// 二是超出了高度,新建一条方针节点的索引节点链,一起最高层头索引节点相同往上长
// Part III:将新建的索引节点(包括头索引节点)与其它索引节点通过右指针衔接在一起
// 这时level是等于旧的最高层级的,自旋
splice: for (int insertionLevel = level;;) {
// h为最高头索引节点
int j = h.level;
// 从头索引节点开端遍历
// 为了便利,这儿叫q为当时节点,r为右节点,d为下节点,t为方针节点相应层级的索引
for (Index q = h, r = q.right, t = idx;;) {
// 假设遍历到了最右边,或许最下边,
// 也便是遍历到头了,则退出外层循环
if (q == null || t == null)
break splice;
// 假设右节点不为空
if (r != null) {
// n是右节点的数据节点,为了便利,这儿直接叫右节点的值
Node n = r.node;
// 比较方针key与右节点的值
int c = cpr(cmp, key, n.key);
// 假设右节点的值为空了,则标明此节点已删去
if (n.value == null) {
// 则把右节点删去
if (!q.unlink(r))
// 假设删去失利,阐明有其它线程先一步修改了,从头来过
break;
// 删去成功后从头取右节点
r = q.right;
continue;
}
// 假设比较c>0,标明方针节点还要往右
if (c > 0) {
// 则把当时节点和右节点别离右移
q = r;
r = r.right;
continue;
}
}
// 到这儿阐明现已到当时层级的最右边了
// 这儿实践是会先走第二个if
// 第一个if
// j与insertionLevel持平了
// 实践是先走的第二个if,j自减后应该与insertionLevel持平
if (j == insertionLevel) {
// 这儿是真实连右指针的当地
if (!q.link(r, t))
// 衔接失利,从头来过
break; // restart
// t节点的值为空,或许是其它线程删去了这个元素
if (t.node.value == null) {
// 这儿会去帮忙删去元素
findNode(key);
break splice;
}
// 当时层级右指针衔接完毕,向下移一层持续衔接
// 假设移到了最下面一层,则阐明都衔接完结了,退出外层循环
if (--insertionLevel == 0)
break splice;
}
// 第二个if
// j先自减1,再与两个level比较
// j、insertionLevel和t(idx)三者是对应的,都是还未把右指针连好的那个层级
if (--j >= insertionLevel && j < level)
// t往下移
t = t.down;
// 当时层级到最右边了
// 那只能往下一层级去走了
// 当时节点下移
// 再取相应的右节点
q = q.down;
r = q.right;
}
}
}
return null;
}
// 寻觅方针节点之前最近的一个索引对应的数据节点
private Node findPredecessor(Object key, Comparator cmp) {
// key不能为空
if (key == null)
throw new NullPointerException(); // don't postpone errors
// 自旋
for (;;) {
// 从最高层头索引节点开端查找,先向右,再向下
// 直到找到方针方位之前的那个索引
for (Index q = head, r = q.right, d;;) {
// 假设右节点不为空
if (r != null) {
// 右节点对应的数据节点,为了便利,咱们叫右节点的值
Node n = r.node;
K k = n.key;
// 假设右节点的value为空
// 阐明其它线程把这个节点符号为删去了
// 则帮忙删去
if (n.value == nullrimming) {
if (!q.unlink(r))
//乐清教科研网 假设删去失利
// 阐明其它线程先删去了,从头来过
break; // restart
// 删去之后从头读取右节点
r = q.right; // reread r
continue;
}
// 假设方针key比右节点还大,持续向右寻觅
if (cpr(cmp, key, k) > 0) {
// 往右移
q = r;
// 从头取右节点
r = r.right;
continue;
}科斯塔沙滩独练
// 假设c<0,阐明不能再往右了
}
// 到这儿阐明当时层级现已到最右了
// 两种状况:一是r==null,二是c<0
// 再从下一级开端找
// 假设没有下一级了,就回来这个索引对应的数据节点
if ((d = q.down) == null)
return q.node;
// 往下移
q = d;
// 从头取右节点
r = d.right;
}
}
}
// Node.class中的办法,帮忙删去元素
void helpDelete(Node b, Node f) {
/*
* Rechecking links and then doing only one of the
* help-out stages per call ten罗振环ds to minimize CAS
* interference among helping threads.
*/
// 这儿的调用者this==n,三者联络是b->n->f
if (f == next && this == b.next) {
// 将n的值设置为null后,会先把n的下个节点设置为marker节点
// 这个marker节点的值是它自己
// 这儿假设不是它自己阐明marker失利了,从头marker
if (f == null || f.value != f) // not already marked
casNext(f, new Node(f));
else
// marker过了,就把b的下个节点指向marker的下个节点
b.casNext(this, f.next);
}
}
// Index.class中的办法,删去succ节点
final boolean unlink(Index succ) {
// 原子更新当时节点指向下一个节点的下一个节点
// 也便是删去下一个节点
return node.value != null && casRight(succ, succ.right);
}
// Index.class中的办法,在当时节点与succ之间刺进newStianlongbabusifuucc节点
final boolean link(Index succ, Index newSucc) {
// 在当时节点与下一个节点中心刺进一个节点
Node n = node;
// 新节点指向当时节点的下一个节点
newSucc.right = succ;
// 原子更新当时节点的下一个节点指向新节点
return n.value != null && casRight(succ, newSucc);
}

咱们这儿把整个刺进进程分红三个部分:

Part I:找到方针节点的方位并刺进

(1)这儿的方针节点是数据节点,也便是最底层的那条链;

(2)寻觅方针节点之前最近的一个索引对应的数据节点(数据节点都是在最底层的链表上);

(3)从这个数据节点开端往后遍历,直到找到方针节点应该刺进的方位;

(4)假设这个方位有元素,就更新其值(onlyIfAbsent=false);

(5)假设这个方位没有元素,就把方针节点刺进;

(6)至此,方针节点现已刺进到最底层的数据节点链表中了;

Part II:随机决议是否需求树立索引及其层次,假设需求则树立自上而下的索引

(1)取个随机数rnd,核算(rnd & 0x80000001);

(2)假设不等于0,完毕刺进进程,也便是不需求创立索引,回来;

(3)假设等于0,才进入创立索引的进程(只需正偶数才会等于0);

(4)核算while (((rnd >>>= 1) & 1) != 0),决议层级数,level从1开端;

(5)假设算出来的层级不高于现有最高层级,则直接树立一条竖直的索引链表(只需down有值),并完毕Part II;

(6)假设算出来的层级高于现有最高层级,则新的层级只能比现有最高层级多1;

(7)相同树立一条竖直的索引链表(只需down有值);

(8)将头索引也向上增加到相应的高度,完毕Part II;

(9)也便是说,假设层级不超越现有高度,只树立一条索引链,不然还要额定增加头索引链的高度(脑补一下,后边举例阐明);

Part III:将新建的索引节点(包括头索引节点)与其它索引节点通过右指针衔接在一起(补上right指针)

(1)从最高层级的头索引节点开端,向右遍历,找到方针索引节点的方位;

(2)假设当时层有方针索引,则把方针索引刺进到这个方位,并把方针索引前一个索引向下移一个层级;

(3)假设当时层没有方针索引,则把方针索引方位前一个索引向下移一个层级;

(4)相同地,再向右遍历,寻觅新的层级中方针索引的方位,回到第(2)步;

(5)顺次循环找到一切层级方针索引的方位并把它们刺进到横向的索引链表中;

总结起来,总共便是三大步:

(1)刺进方针节点到数据节点链表中;

(2)树立竖直失望之塔97的dow变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞appn链表;

(3)树立横向的right链表;

增加元素举例

假定初沈隽寒始变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞app链表是这样:

假设,咱们现在要刺进一个元素9。

(1)寻觅方针节点之前最近的一个索引对应的数据节点,在这儿也便是找到了5这个数据节点;

(2)从5开端向后遍历,找到方针节点的方位,也便是在8和12之间;

(3)刺进9这个元素,Part I 完毕;

然后,核算其索引层级,假设是3,也便是level=3。

(1)树立竖直的down索引链表;

(2)超越了现有高度2,还要再增加head索引链的高度;

(3)至此,Part II 完毕;

最终,把right指针补齐。

(1)从第3层的head往右找当时层级方针索引的方位;

(2)找到就把方针索引和它前面索引的righkb店t指针连上,这儿前一个正好是head;

(3)然后前一个索引向下移,这儿便是head下移;

(4)再往右找方针索引的方位;

(5)找到了就把right指针连上,这儿前一个是3的索引;

(6)然后3的索引下移;

(7)再往右找方针索引的方位;

(8)找到了就把right指针搏斗海豚连上,这儿前一个是5的索引;

(9)然后5下移,究竟了,Part III 完毕,整个刺进进程完毕;

是不是很简略^^

删去元素

删去元素,便是把各层级中对应的元素删去即可,真的这么简略吗?来让咱们上代码:

public V remove(Object key) {
return doRemove(key, null);
}
final V doRemove(Object key, Object value) {
// key不为空
if (key == null)
throw new NullPointerException();
Comparator cmp = comparator;
// 自旋
outer: for (;;) {
// 寻觅方针节点之前的最近的索引节点对应的数据节点
// 为了便利,这儿叫b为当时节点,n为下一个节点,f为下下个节点
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
// 整个链表都遍历完了也没找到方针节点,退出外层循环
if (n == null)
break outer;
// 下下个节点
Node f = n.next;
// 再次查看
// 假设n不是b的下一个节点了
// 阐明有其它线程先一步修改了,从头来过
if (n != b.next) // inconsistent read
break;
// 假设下个节点的值奕为null了
// 阐明有其它线程符号该元素为删去状况了
if ((v = n.value) == null) { // n is deleted
// 帮忙删去
n.helpDelete(b, f);
break;
}
// 假设b的值为空或许v等于n,阐明b已被删去
// 这时分n便是暗恋公式风染白完整版marker节点,那b便是被删去的那个
if (b.value == null || v == n) // b is deleted
break;
// 假设c<0,阐明没找到元素,退出外层循环
if ((c = cpr(cmp, key, n.key)) < 0)
break outer;
// 假设c>0,阐明还没找到,持续向右找
if (c > 0) {
// 当时节点往后移
b = n;
// 下一个节点往后移
n = f;
continue;
}
// c=0,阐明n便是要找的元素
// 假设value不为空且不等于找到元素的value,不需求删去,退出外层循环
if (value != null && !value.equals(v))
break outer;
// 假设value为空,或许持平
// 原子符号n的value值为空
if (!n.casValue(v, null))
// 假设删去失利,阐明其它线程先一步修改了,从头来过
break;
// P.S.到了这儿n的值肯定是设置成null了
// 要害!!!!
// 让n的下一个节点指向一个market节点
// 这个market节点的key为null,value为mark腿打开er自己,next为n的下个节点f
// 或许让b的下一个节点指向下下个节点
// 留意:这儿是或许||,由于两个CAS不能确保都成功,只能一个一个去测验
// 这儿有两层意思:
// 一是假设符号market成功,再测验将b的下个节点指向下下个节点,假设第二步失利了,进入条件,假设成功了就不必进入条件了
// 二是假设符号market失利了,直接进刘晟豪入条件
if (!n.appendMarker(f) || !b.casNext(n, f))
// 通过findNode()重试删去(里边有个helpDelete()办法)
findNode(key); // retry via findNode
else {
// 上面两步操作都成功了,才会进入这儿,不太好了解,上面两个条件都有非"!"操作
// 阐明节点现已删去了,通过findPredecessor()办法删去索引节点
// findPredecessor()里边有unlink()操作
findPredecessor(key, cmp); // clean index
// 假设最高层头索引节点没有右节点,则跳表的高度降级
if (head.right == null)
tryReduceLevel();
}
// 回来删去的元素值
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
}
return null;
}

(1)寻觅方针节点之前最近的一个索引对应的数据节点(数据节点都是在最底层的链表上);

(2)从这个数据节点开端往后遍历,直到找到方针节点的方位;

(3)假设这个方位没有元素,直接回来null,标明没有要删去的元素;

(4)假设这个方位有元素,先通过n.casValue(v, null)原子更新把其value设置为null;

(5)通过n.appendMarker(f)在当时元素后边增加一个marker元素符号当时元素是要删去的元素;

(6)通过b.casNext(n, f)测验删去元素;

(7)假设上面两步中的恣意一步失利了都通过findNode(key)中的n.helpDelete(b, f)再去不断测验删去;

(8)假设上面两步都成功了变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞app,再通过findPredecessor(key, cmp)中的q.unlink(r)删去索引节点;

(9)假设head的right指针指向了null,则跳表高度降级;

删去元素举例

假设初始跳表如下图所示,咱们要删去9这个元素。

(1)找到9这个数据节点;

(2)把9这个节点的value值设置为null;

(3)在9后边增加一个marker节点,符号9现已删去了;

(4)让8指向12;

(5)把索引节点与它前一个索引的right断开联络;

(6)跳表高度降级;

至于,为什么要有(2)(3)(4)这么多进程呢,由于多线程下假设直接让8指向12,能够其它线程先一步在9和12间刺进了一个元素10呢,这时分就不对了。

所以这儿搞了三步来确保多线程下操作的正确性。

假设第(2)步失利了,则直接重试;

假设第(3)或(4)步失利了,由于第(2)步是成功的,则通过helpDelete()不断重试去删去;

其实helpDelete()里边也是不断地重试(3)和(4);

只需这三步都正确完结了,才干阐明这个元素完全被删去了。

这一块结合上面图中的红绿蓝色好好了解一下,一定要想在并发环境中会怎么样。

查找元素

通过上面的刺进和删去,查找元素就比较简略了,直接上代码:

public V get(Object key) {
return doGet(key);
}
private V doGet(Object key) {
// key不为空
if (key == null)
throw new NullPointerException();
Comparator cmp = comparator;
// 自旋
outer: for (;;) {
// 寻觅方针节点之前最近的索引对应的数据节点
// 为了便利,这儿叫b为当时节点,n为下个节点,f为下下个节点
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
// 假设链表到头还没找到元素,则跳出外层循环
if (n == null)
break outer;
// 下下个节点
Node f = n.next;
// 假设不一致读,从头来过
if (n != b.next) // inconsistent read
break;
// 假设n的值为空,阐明节点已被其它线程符号为删去
if ((v = n.value) == null) { // n is deleted
// 帮忙删去,再重试
n.helpDelete(b, f);
break;
}
// 假设b的值为空或许v等于n,阐明b已被删去
// 这时分n便是marker节点,那b便是被删去的那个
if (b.value == null || v == n) // b is deleted
break;
// 假设c==0,阐明找到了元素,就回来元素值
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
// 假设c<0,阐明没找到元素
if (c < 0)
break outer;
// 假设c>0,阐明还没找到,持续寻觅
// 当时节点往后移
b = n;
// 下一个节点往后移
n = f;
}
}
return null;
}

(1)寻觅方针节点之前最近的一个索引对应的数据节点whiteeeen(数据节点都是在最底层的链表上);

(2)从这个数据节点开端往后遍历,直到找到方针节点的方位;

(3)假设这个方位没有元素,直接回来null,标明没有找到元素;

(4)假设这个方位有元素,回来元素的value值;

查找元素举例

假设有如下图所示这个跳表,咱们要查找9这个元素,它走过的途径是怎样的呢?或许跟你相像的不相同。。

(1)寻觅方针节点之前最近的一个索引对应的数据节点,这儿便是5;

(2)从5开端往后遍历,通过8,到9;

(3)找到了回来;

整个途径如下图所示:

是不是很操蛋?

为啥不从9的索引直接过来呢?

从我实践打断点调试来看确实是依照上图的途径来走的。

我猜想或许是由于findPredecessor()这个办法是刺进、删去、查找元素多个办法共用的,在单链表中刺进和删去元素是需求记载前一个元素的,而查找并不需求,这儿为了兼容三者使得编码相对简略一点,所变形金刚4,如皋天气预报-ope电竞竞猜_ope体育电竞官方网站_ope体育电竞app以就运用了相同的逻辑,而没有独自对查找元素进行优化。

相关新闻

admin

admin

TA太懒了...暂时没有任何简介

精彩新闻