欢迎来到皮皮网网首页

【庄家显影指标源码】【房产超市源码】【网站源码编写】cas修改源码_cas 源码

来源:病毒源码手机版 时间:2024-12-24 04:20:27

1.画面设置cas是改源什么意思?
2.Linux基础组件之无锁消息队列ypipe/yqueue详解
3.ReentrantLock 源码解析 | 京东云技术团队
4.C#中使用CAS实现无锁算法
5.为什么会有 AtomicReference ?
6.简单说说ConcurrentSkipListMap

cas修改源码_cas 源码

画面设置cas是什么意思?

       CAS是Central Authentication Service的缩写,即集中式认证服务。源码它是改源一种用于Web应用程序的单点登录协议。CAS协议通过认证中心(服务器)来给多个服务提供认证服务,源码用户一次登录认证以后,改源便可以访问被授权的源码庄家显影指标源码多个服务。CAS协议是改源一种开放源代码的协议,被广泛应用于大型企业和组织的源码身份认证系统中。

       CAS需要先部署一个认证服务器和多个应用程序服务器,改源然后在这些服务器之间建立信任关系。源码用户首次登录时,改源应该重定向到认证服务器,源码输入用户名和密码进行认证,改源并且一旦通过认证,源码用户将被重定向回要访问的改源应用程序服务器。以后的每次访问都无需再次认证。认证服务器和应用程序服务器之间使用安全令牌和Session来保障安全性。

       CAS的优点在于提供可靠的身份验证,减少了用户访问多个Web应用程序时的不必要的登录操作,避免了重复输入用户名和密码等问题。它广泛应用于大型企业和组织的身份认证系统中,例如教育机构、银行、保险公司、医院等。CAS的使用可以帮助企业或组织节省时间和成本,减少安全漏洞,提高用户体验并提高整个系统的房产超市源码安全性。

Linux基础组件之无锁消息队列ypipe/yqueue详解

       CAS定义

       比较并交换(compare and swap, CAS),在多线程编程中用于实现不被打断的数据交换,避免数据不一致问题。该操作通过比较内存值与指定数据,当数值相同则替换内存数据。

       为什么需要无锁队列

       锁引起的问题:cache损坏/失效、同步机制上的争抢、动态内存分配。

       有锁导致线程切换引发cache损坏

       大量线程切换导致cache数据失效,处理器与主存之间数据传输效率下降,影响性能。

       在同步机制上的争抢队列

       阻塞队列导致任务暂停或睡眠,大量时间浪费在获取互斥锁,而非处理数据,引发严重争用。

       动态内存分配

       多线程中动态分配内存导致互斥,线程频繁分配内存影响应用性能。

       无锁队列的实现

       无锁队列由ypipe_t和yqueue_t类构成,适用于一读一写场景。通过chunk模式批量分配结点,减少动态内存分配的互斥问题。批量分配大小根据业务场景调整,通常设置较大较为安全。利用spare_chunk存储未释放的chunk,降低频繁分配释放。预写机制减少CAS调用。巧妙的网站源码编写唤醒机制,读端等待无数据时进入等待状态,写端根据返回值判断队列是否为空以唤醒读端。

       无锁队列使用

       yqueue.write(count,false)用于写入元素并标记完成状态,yqueue.flush()使读端可见更新后数据。yqueue.read(&value)读取元素,返回true表示读到元素,返回false表示队列为空。

       ypipe_t使用

       write(val, false)更新写入位置,flush()刷新数据到管道,read()读取数据并更新可读位置。

       yqueue_t构造函数

       初始化队列,end_chunk总是指向最后分配的chunk,back_chunk仅在有元素插入时指向对应的chunk。

       front()和back()函数

       返回队列头和尾的可读写元素位置。

       push()和pop()函数

       push()更新写入位置,pop()更新读取位置并检测释放chunk,保持数据流。

       源码分析

       yqueue_t内部使用chunk批量分配,减少内存操作,spare_chunk存储释放的chunk以供再次使用。ypipe_t构建单写单读无锁队列,通过CAS操作控制读写位置,实现高效数据交换。

       ypipe_t / yqueue_t无锁队列利用chunk机制避免频繁内存动态分配,提升性能。通过局部性原理复用回收的chunk,减少资源消耗。祖玛游戏源码flush()检测队列状态通知唤醒,优化数据交换过程。

ReentrantLock 源码解析 | 京东云技术团队

       并发指同一时间内进行了多个线程。并发问题是多个线程对同一资源进行操作时产生的问题。通过加锁可以解决并发问题,ReentrantLock 是锁的一种。

       1 ReentrantLock

       1.1 定义

       ReentrantLock 是 Lock 接口的实现类,可以手动的对某一段进行加锁。ReentrantLock 可重入锁,具有可重入性,并且支持可中断锁。其内部对锁的控制有两种实现,一种为公平锁,另一种为非公平锁.

       1.2 实现原理

       ReentrantLock 的实现原理为 volatile+CAS。想要说明 volatile 和 CAS 首先要说明 JMM。

       1.2.1 JMM

       JMM (java 内存模型 Java Memory Model 简称 JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量的访问方式.

       由于 JMM 运行的程序的实体是线程。而每个线程创建时 JMM 都会为其创建一个自己的工作内存 (栈空间), 工作内存是每个线程的私有数据区域。而 java 内存模型中规定所有的变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程的变量的操作 (读取赋值等) 必须在自己的工作内存中去进行,首先要将变量从主存拷贝到自己的工作内存中,然后对变量进行操作,操作完成后再将变量操作完后的行业站源码新值写回主内存,不能直接操作主内存的变量,各个线程的工作内存中存储着主内存的变量拷贝的副本,因不同的线程间无法访问对方的工作内存,线程间的通信必须在主内存来完成。

       如图所示:线程 A 对变量 A 的操作,只能是从主内存中拷贝到线程中,再写回到主内存中。

       1.2.2 volatile

       volatile 是 JAVA 的关键字用于修饰变量,是 java 虚拟机的轻量同步机制,volatile 不能保证原子性。 作用:

       作用:CAS 会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读 - 改 - 写操作。

       1.2.4 AQSAQS 的全称是 AbstractQueuedSynchronizer(抽象的队列式的同步器),AQS 定义了一套多线程访问共享资源的同步器框架。

       AQS 主要包含两部分内容:共享资源和等待队列。AQS 底层已经对这两部分内容提供了很多方法。

       2 源码解析

       ReentrantLock 在包 java.util.concurrent.locks 下,实现 Lock 接口。

       2.1 lock 方法

       lock 分为公平锁和非公平锁。

       公平锁:

       非公平锁:上来先尝试将 state 从 0 修改为 1,如果成功,代表获取锁资源。如果没有成功,调用 acquire。state 是 AQS 中的一个由 volatile 修饰的 int 类型变量,多个线程会通过 CAS 的方式修改 state,在并发情况下,只会有一个线程成功的修改 state。

       2.2 acquire 方法

       acquire 是一个业务方法,里面并没有实际的业务处理,都是在调用其他方法。

       2.3 tryAcquire 方法

       tryAcquire 分为公平和非公平两种。

       公平:

       非公平:

       2.4 addWaiter 方法

       在获取锁资源失败后,需要将当前线程封装为 Node 对象,并且插入到 AQS 队列的末尾。

       2.5 acquireQueued 方法

       2.6 unlock 方法

       释放锁资源,将 state 减 1, 如果 state 减为 0 了,唤醒在队列中排队的 Node。

       3 使用实例

       3.1 公平锁

       1. 代码:

       2. 执行结果:

       3. 小结:

       公平锁可以保证每个线程获取锁的机会是相等的。

       3.2 非公平锁

       1. 代码:

       2. 执行结果:

       3. 小结:

       非公平锁每个线程获取锁的机会是随机的。

       3.3 忽略重复操作

       1. 代码:

       2. 执行结果:

       3. 小结:

       当线程持有锁时,不会重复执行,可以用来防止定时任务重复执行或者页面事件多次触发时不会重复触发。

       3.4 超时不执行

       1. 代码:

       2. 执行结果:

       3. 小结:

       超时不执行可以防止由于资源处理不当长时间占用资源产生的死锁问题。

       4 总结

       并发是现在软件系统不可避免的问题,ReentrantLock 是可重入的独占锁,比起 synchronized 功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等,是处理并发问题很好的解决方案。

C#中使用CAS实现无锁算法

       CAS(Compare-and-Swap)操作是一种多线程并发编程中常用的原子操作,用于实现多线程间的同步和互斥访问。它通过比较内存地址处的值与期望的旧值是否相等来实现这一目标。若相等,则将新值写入该内存地址;否则不做任何操作。CAS 操作的原子性由硬件层面的CPU指令保证,通常通过 Interlocked 类在 C# 中实现。

       在C#中,我们使用Interlocked类的CompareExchange方法来实现CAS操作。此方法接收三个参数:内存地址、期望的旧值和新值。如果内存地址处的值与期望的旧值相等,则将新值写入该内存地址并返回旧值;否则不执行任何操作。通过判断返回值与期望的旧值是否相等,我们可以得知CompareExchange操作是否成功。

       在使用CAS实现无锁算法时,我们通常需要在更新数据后执行进一步的操作。结合while(true)循环,我们可以在每次尝试更新数据后检查是否成功。如果失败,则继续尝试,直到成功为止。

       以下是一个简单的计数器示例,它使用CAS实现了一个线程安全的自增操作。在CLR底层源码中,我们也经常看到使用类似方法实现线程安全计数器的代码。同时,队列类也使用CAS实现线程安全的入队和出队操作,该操作更为复杂,需要不断检查是否有其他线程修改数据。

       在复杂的无锁算法中,每一步操作都必须考虑是否被其他线程修改。每一步操作非原子,因此我们不仅依赖CAS操作,还必须确保在执行每个操作前检查数据是否被修改。类比薛定谔的猫,我们不知道数据状态直到尝试修改时才确定。

       通过测试代码,我们可以观察到在一定数量的操作中,需要重试的次数。这个重试次数取决于队列中是否有数据可供操作,而在多线程环境下,每次操作的结果可能有所不同。

       CAS是一种乐观锁机制,假设数据未被其他线程修改,若未修改则直接修改,若已修改则重新获取数据并再次尝试修改。在实现复杂的数据结构时,我们不仅依赖CAS操作,还需注意数据是否被其他线程修改,以及处理可能的分支情况。

为什么会有 AtomicReference ?

       原子性工具类AtomicReference是Java.util.concurrent.atomic包下的一个类,它能够确保在修改对象引用时的线程安全性。例如在处理账户问题时,多个线程可能同时向同一个账户存入款项,使用AtomicReference可以避免这种情况导致的数据不一致问题。

       在使用AtomicReference时,我们需要了解它的基本使用方法。首先声明一个全局变量BankCard,并使用volatile关键字对其进行修饰,以确保在对其引用进行变化后对其他线程可见。然后,我们可以通过AtomicReference来封装BankCard的引用,使用get()方法获得原子性的引用,接着使用CAS(Compare and Swap)乐观锁进行非阻塞更新。这样可以确保在修改引用时的线程安全性。

       AtomicReference源码解析中,我们发现它主要依赖于sun.misc.Unsafe类的native方法来保证操作的原子性。Unsafe的objectFieldOffset方法可以获取成员属性在内存中的地址相对于对象内存地址的偏移量,这个偏移量就是valueOffset,方便后续通过内存地址直接进行操作。value是AtomicReference的实际值,由于使用了volatile,这个值实际上就是内存值。

       AtomicReference与AtomicInteger非常相似,它们内部都使用了Unsafe、value、valueOffset等属性。get()和set()方法分别可以原子性地读取和设置AtomicReference中的数据。lazySet方法则在没有内存屏障的情况下读写变量,以减少开销。getAndSet方法则调用unsafe中的getAndSetObject方法,涉及getObjectVolatile和compareAndSwapObject方法,它们在do...while循环中,每次获取最新对象引用的值,如果使用CAS成功交换两个对象,则直接返回更新前的内存值,即旧值。

       AtomicReference的关键方法CAS(Compare and Swap)在compareAndSet方法中实现,与AtomicInteger不同的是,AtomicReference调用compareAndSwapObject方法。这段代码底层使用了Atomic:cmpxchg方法进行CAS交换,并将旧值进行decode返回。

       weakCompareAndSet方法在JDK1.8中与compareAndSet方法完全相同,但实际上这是JDK源码设计的巧妙之处,用于处理特定场景下的线程安全问题。

       在使用AtomicReference时,我们需要充分了解它的特性和源码实现,以确保在多线程环境下正确地管理和更新对象引用。本文主要介绍了AtomicReference的出现背景、使用场景以及源码分析,涵盖了网络上关于AtomicReference的大部分内容。

简单说说ConcurrentSkipListMap

       åŸºæœ¬ä»‹ç»

       è·³è·ƒè¡¨çš„性质如下:

       æœ€åº•å±‚的数据节点按照关键字key升序排列;

       åŒ…含多级索引,每个级别的索引节点按照其关联的数据节点的关键字key升序排列;

       é«˜çº§åˆ«ç´¢å¼•æ˜¯å…¶ä½Žçº§åˆ«ç´¢å¼•çš„子集;

       å¦‚果关键字key在级别level=i的索引中出现,则级别level<=i的所有索引都包含该key。

       è·³è·ƒè¡¨ConcurrentSkipListMap的数据结构如下图所示,下图一共有三层索引,最底下为数据节点,同一层索引中,索引节点之间使用right指针相连,上层索引节点的down指针指向下层的索引节点。

源码分析核心字段分析

       head 指向 node(BASE_HEADER) 的顶层索引。

/***Thetopmostheadindexoftheskiplist.*/privatetransientvolatileHeadIndex<K,V>head;

       BASE_HEADER 头结点,即最顶层索引的头节点的value值

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()

       Node 静态内部类,即数据节点

/***数据节点*/staticfinalclassNode<K,V>{ finalKkey;//数据节点的keyvolatileObjectvalue;//数据节点的valuevolatileNode<K,V>next;//指向下一个数据节点/***Createsanewregularnode.*/Node(Kkey,Objectvalue,Node<K,V>next){ this.key=key;this.value=value;this.next=next;}}

       Index 静态内部类,即普通索引节点

/***普通索引节点*/staticclassIndex<K,V>{ finalNode<K,V>node;//索引节点指向的数据节点finalIndex<K,V>down;//当前索引节点的正下方索引节点volatileIndex<K,V>right;//当前索引节点的右索引节点/***Createsindexnodewithgivenvalues.*/Index(Node<K,V>node,Index<K,V>down,Index<K,V>right){ this.node=node;this.down=down;this.right=right;}}

       HeadIndex 静态内部类,即当前级别索引的头节点

/***当前级别索引的头节点*/staticfinalclassHeadIndex<K,V>extendsIndex<K,V>{ finalintlevel;//所处索引级别/***node:当前索引指向的数据节点*down:当前索引节点的正下方索引节点*right:当前索引节点的右索引节点*level:当前索引头节点所处的索引级别*/HeadIndex(Node<K,V>node,Index<K,V>down,Index<K,V>right,intlevel){ super(node,down,right);this.level=level;}}查询

       æ ¹æ®æŒ‡å®šçš„key查询节点,源码如下:

publicVget(Objectkey){ //调用doGet方法returndoGet(key);}/***真正实现查询方法*/privateVdoGet(Objectkey){ if(key==null)thrownewNullPointerException();Comparator<?superK>cmp=comparator;outer:for(;;){ for(Node<K,V>b=findPredecessor(key,cmp),n=b.next;;){ Objectv;intc;if(n==null)breakouter;Node<K,V>f=n.next;if(n!=b.next)//inconsistentreadbreak;if((v=n.value)==null){ //nisdeletedn.helpDelete(b,f);break;}if(b.value==null||v==n)//bisdeletedbreak;if((c=cpr(cmp,key,n.key))==0){ @SuppressWarnings("unchecked")Vvv=(V)v;returnvv;}if(c<0)breakouter;b=n;n=f;}}returnnull;}

       åœ¨ä¸Šè¿°ä»£ç ä¸­ï¼Œouter处的for自旋中,首先查看findPredecessor:查询指定key节点的前驱节点。该方法在下面的好多地方会调用,例如插入元素,删除元素以及删除元素对应的索引时都会调用。

       findPredecessor方法源码如下:

/***作用1:找到key对应节点的前驱节点,不一定的真的前驱节点,也可能是前驱结点的前驱节点*作用2:删除无效的索引,即要删除节点时,将节点的索引也删除掉*/privateNode<K,V>findPredecessor(Objectkey,Comparator<?superK>cmp){ if(key==null)thrownewNullPointerException();//don'tpostponeerrorsfor(;;){ //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;Kk=n.key;//该节点已经删除,需要删除其对应的索引if(n.value==null){ //该节点已经删除,需要删除其对应的索引if(!q.unlink(r))break;//restartr=q.right;//rereadrcontinue;}//当前查找的key比r节点的key大,所以r、q节点都向右移动if(cpr(cmp,key,k)>0){ q=r;r=r.right;continue;}}//当q的下方索引节点为空,则说明已经到数据节点层了,需要退出进行后续查找处理if((d=q.down)==null)returnq.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节点指向的数据节点为,节点的key比6节点的key大,此时需要执行如下代码:

/***此时当前查找的key小于r节点的key,需要往下一级索引查找*d节点赋值为为q节点为正下方节点,即下一级索引的正下方节点*/q=d;r=d.right;

       æ­¤æ—¶r节点指向的数据节点为5,5节点的key比6节点的key小,q、r节点向右移动,如下图所示

       æ­¤æ—¶r节点指向的数据节点为,节点的key比6节点的key大,同理需要往下级索引走,如下图所示:

       æ­¤æ—¶r节点指向的数据节点为,节点的key比6节点的key大,同理需要往下级索引走,但是此时下一级索引为空了,即(d = q.down) == null了,此时执行的代码如下, 返回q索引指向的节点,即返回节点5.

//当q的下方索引节点为空,则说明已经到数据节点层了,需要退出进行后续查找处理if((d=q.down)==null)returnq.node;

       ä»¥ä¸Šå°±æ˜¯æ–¹æ³•findPredecessor的查找流程,咱们接着继续看上面的doGet方法

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()0

       é¦–先初始化b、n、f三个节点,如下图所示

        发现此时n节点指向的节点就是要查询的节点,于是执行如下代码:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()1

       ç›´æŽ¥è¿”回n节点的value值。查询操作完成。

插入

       è·³è·ƒè¡¨çš„插入操作分以下四种情况:

       æƒ…况1:跳跃表内存在key一致元素,做替换

       æƒ…况2:插入新元素,无须给新元素生成索引节点

       æƒ…况3:插入新元素,需要给新元素生成索引节点,且索引高度 < maxLevel

       æƒ…况4:插入新元素,需要给新元素生成索引节点,且索引高度 > maxLevel

       æºç å¦‚下:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()2

       é¦–先还是跟查询操作类似,调用findPredecessor方法先查找到待插入key的前驱节点,举个例子,例如我们想要插入节点7,如下图所示:

       æŽ¥ç€è·ŸæŸ¥è¯¢æ“ä½œä¸€æ ·çš„步骤如下,直接看图:

        此时r节点指向数据节点1,节点1的key小于待插入的节点7的key,于是节点q、r同时向右移动。

       æ­¤æ—¶r节点指向数据节点,节点的key大于待插入节点7的key,于是往下一层索引继续查找,执行的代码如下:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()3

       åŽé¢çš„操作类似

       æ­¤æ—¶r节点的key大于待插入的节点6的key,但是q节点的down指针已为空,此时直接返回q节点指向的节点5。

       æŽ¥ç€å›žåˆ°doPut方法,先来查看outer循环,如下:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()4

       é¦–先初始化三个节点b、n、f,n节点为b节点的下一个节点,而f节点为n节点的下一个节点,如下图所示

       æŽ¥ç€æ¯”较节点n与待插入的key的大小,此时n节点的key小于待插入节点的key,于是b、n、f三个节点均向下移动如下图所示

       æ­¤æ—¶n节点的key大于待插入的key,此时执行如下代码,通过cas方式修改b节点的下一个节点为z节点,接着跳出outer循环。

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()5

       ç„¶åŽæˆ‘们知道doPut剩下的代码无非就是判断是否给新插入的节点z创建索引,如果需要创建对应的索引。

       é¦–先通过int rnd = ThreadLocalRandom.nextSecondarySeed();计算出一个随机数,接着进行如下判断:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()6

       å¦‚æžœrnd & 0x) == 0就给新插入的z节点创建索引,我们知道0x = 即最高位和最后一位为1,其余全部是0,

       æ¡ä»¶ï¼š(rnd & 0x) == 0什么时候成立?

       rnd这个随机数最低位和最高位同时是0的时候,条件成立,概率是1/4

       ä¸¾ä¸ªä¾‹å­ï¼šä¾‹å¦‚rnd = = 3条件就成立。

       å¦‚果条件成立的话,接着计算到底给z节点创建几级索引,代码如下:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()7

       é€šè¿‡while条件((rnd >>>= 1) & 1) != 0满足几次就创建几级索引。例如:

       rnd = 计算出来的level => 3

       rnd = 计算出来的level => 8

       ç„¶åŽæŽ¥ç€æ¯”较计算出来的z节点的索引跟现有的跳跃表的索引级别大小。

       æƒ…况一:z节点计算出来的索引level比跳跃表的level小

       æƒ…况二:z节点计算处理的索引level比跳跃表的level大。此时会选择最终的level为原来的调表的level + 1

       æƒ…况一

       ç»™z节点创建索引的步骤如下图所示,此时z节点的索引还没有加入跳跃表现有的索引队列中

       æŽ¥ç€ç»§ç»­æ‰§è¡Œsplice循环,代码如下:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()8

       åˆå§‹åŒ–q、r节点如下图所示

       æ­¤æ—¶r节点的key比新插入z节点,即7节点小,于是两个节点q、t都向右移动如下图所示

       æ­¤æ—¶r节点的key比新插入z节点,即7节点大,执行如下代码:

/***Specialvalueusedtoidentifybase-levelheader*/privatestaticfinalObjectBASE_HEADER=newObject()9

       æ­¤æ—¶r节点的key比新插入z节点,即7节点小,于是两个节点q、t都向右移动如下图所示

       æ­¤æ—¶r节点的key比新插入z节点,即7节点大,同理,直接看图

       æƒ…况二

       è·Ÿæƒ…况一类似,这里就不一一画图了

删除

       åˆ é™¤æ–¹æ³•å®Œæˆçš„任务如下:

       è®¾ç½®æŒ‡å®šå…ƒç´ value为null

       å°†æŒ‡å®šnode从node链表移除

       å°†æŒ‡å®šnode的index节点 从 对应的 index 链表移除

/***数据节点*/staticfinalclassNode<K,V>{ finalKkey;//数据节点的keyvolatileObjectvalue;//数据节点的valuevolatileNode<K,V>next;//指向下一个数据节点/***Createsanewregularnode.*/Node(Kkey,Objectvalue,Node<K,V>next){ this.key=key;this.value=value;this.next=next;}}0

       åŒæ ·ï¼Œé¦–先通过findPredecessor方法查找到要删除key的前驱节点,就不一一画图了,直接看找到的前驱节点的图,如下:

       æŽ¥æ¯”较n节点的key与待删除的key的大小,此时n节点的key小于待删除的key,即7节点的key,于是将b、n、f三个节点都向右移动,如下图:

       æ­¤æ—¶n节点的key跟待删除的key一样,于是执行如下代码:

/***数据节点*/staticfinalclassNode<K,V>{ finalKkey;//数据节点的keyvolatileObjectvalue;//数据节点的valuevolatileNode<K,V>next;//指向下一个数据节点/***Createsanewregularnode.*/Node(Kkey,Objectvalue,Node<K,V>next){ this.key=key;this.value=value;this.next=next;}}1

       æœ€åŽå†è°ƒç”¨findPredecessor清楚无效的索引,即上面删除的节点的索引。

/***数据节点*/staticfinalclassNode<K,V>{ finalKkey;//数据节点的keyvolatileObjectvalue;//数据节点的valuevolatileNode<K,V>next;//指向下一个数据节点/***Createsanewregularnode.*/Node(Kkey,Objectvalue,Node<K,V>next){ this.key=key;this.value=value;this.next=next;}}2

       é‡ç‚¹é å¦‚下代码块删除索引的:

/***数据节点*/staticfinalclassNode<K,V>{ finalKkey;//数据节点的keyvolatileObjectvalue;//数据节点的valuevolatileNode<K,V>next;//指向下一个数据节点/***Createsanewregularnode.*/Node(Kkey,Objectvalue,Node<K,V>next){ this.key=key;this.value=value;this.next=next;}}3

       æˆ‘们知道在上面已经将待删除的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节点的索引一一删除完,最终的图下所示