JDK的跳表源码分析
JDK源码中的跳表实现类: ConcurrentSkipListMap和ConcurrentSkipListSet。
其中ConcurrentSkipListSet的实现是基于ConcurrentSkipListMap。因此下面具体分析ConcurrentSkipListMap的实现:
//查找指定Key的前置节点
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) { if (key == null) throw new NullPointerException(); // don't postpone errors for (;;) { 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)) // 如果节点r=q.right为空,则删除该节点r,即把节点q.right指向r.right. break; // restart 然后跳出本次循环,从头节点开始继续循环。 r = q.right; // reread r continue; } if (cpr(cmp, key, k) > 0) {// 通过Key值于当前节点的right节点比较,如果Key值较大,则继续往右比较 q = r; r = r.right; continue; } } if ((d = q.down) == null) // 如果当前节点的down为空,则当前链表为最底层链表,该节点的值<=key,此即为查询结果。 return q.node; q = d; // 如果Key值不比当前节点的right节点大,则继续往下比较 r = d.right; } } } // 根据Key查找对应的节点 private Node<K,V> findNode(Object key) { if (key == null) throw new NullPointerException(); // don't postpone errors Comparator<? super K> cmp = comparator; outer: for (;;) { for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {//从key的前置节点开始查找 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 下一个节点为null,则删除该节点,重新开始查找 n.helpDelete(b, f); break; } if (b.value == null || v == n) // b is deleted break; if ((c = cpr(cmp, key, n.key)) == 0) //查找到,则返回结果 return n; if (c < 0) break outer; b = n; n = f; } } return null; }
private V doGet(Object key) 方法的实现与findNode一致,只是返回值为Value的复制。
新增一个节点的过程如下:
/** * * Main insertion method. Adds element if not present, or * replaces value if present and onlyIfAbsent is false. * @param key the key * @param value the value that must be associated with key * @param onlyIfAbsent if should not insert if already present * @return the old value, or null if newly inserted * 新增一个节点过程: * 1,根据新增的节点key值,寻找其合适的插入位置b; * 2,如果存在相等的key值,则根据onlyIfAbsent决定是否更新对应的Value值,然后返回; * 3,如果不存在相等的key值,则创建一个新的节点,并插入到合适的位置b,此时操作的是跳表的最底层; * 4,根据随机函数,决定是否添加上层节点,如果不需要添加,则直接返回null; * 5,如果需要添加上层节点,则获取随机值level; * 6,如果随机值level不大于当前最大层数,则创建一个从第一层到第level层的新的节点Index链表,其通过down指针连接,right指针都设置为null; * 7,如果随机值level大于当前最大层数,则跳表的最大层数加1,然后创建一个从第一层到新的最大层的新的节点Index链表,其通过down指针连接,right指针都设置 * 为null;然后从旧的最大层数+1到新的最大层数间新增head节点链表,其通过down指针连接,right指针指向刚新增的对应层的Index节点; * 8,从旧的最大层数开始往最底层,把新增的index节点插入到合适的位置,即更新其right指针(完善第6步的操作)。至此,完成新增节点的整个过程。 */ private V doPut(K key, V value, boolean onlyIfAbsent) { Node<K,V> z; // added node if (key == null) throw new NullPointerException(); Comparator<? super K> cmp = comparator; outer: for (;;) { for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {// 根据Key找到前置节点,然后开始查找 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)) {//如果存在key相等的节点,则如果onlyIfAbsent=false,则通过casValue更新Key对应的Value值。如果onlyIfAbsent=true,则不更新Key对应的Value值,然后返回oldValue。 @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); // 没有查找到对应的Key节点,则新增一个节点 if (!b.casNext(n, z)) // 把新增的节点z设为当前节点的next节点;原子操作,失败则不断的循环操作 break; // restart if lost race to append to b break outer; } } int rnd = ThreadLocalRandom.nextSecondarySeed(); if ((rnd & 0x80000001) == 0) { // test highest and lowest bits //8000000001 = 1000 0000 0000 0000 0000 0000 0000 0001 测试最高位和最低位是否为0 int level = 1, max; while (((rnd >>>= 1) & 1) != 0) //无符号右移1位, 随机获得level值 ++level; Index<K,V> idx = null; HeadIndex<K,V> h = head; if (level <= (max = h.level)) { for (int i = 1; i <= level; ++i) idx = new Index<K,V>(z, idx, null); } else { // try to grow by one level 使整个跳表的level增长1 level = max + 1; // hold in array and later pick the one to use @SuppressWarnings("unchecked")Index<K,V>[] idxs = (Index<K,V>[])new Index<?,?>[level+1]; for (int i = 1; i <= level; ++i) idxs[i] = idx = new Index<K,V>(z, idx, null); //包含如下两步操作 //idx = new Index<K,V>(z,idx,null); 设置idx值,并设置其down和right值 //idxs[i] = idx; 设置每一层中新增的Index节点,其right值都设为null,down值设置为其下一层的Index节点。 for (;;) { h = head; int oldLevel = h.level; if (level <= oldLevel) // lost race to add level break; HeadIndex<K,V> newh = h; Node<K,V> oldbase = h.node; for (int j = oldLevel+1; j <= level; ++j) //设置新增的Head节点,设置其node,down,right和level值 newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j); if (casHead(h, newh)) { //更新head值成功,则退出无限循环 h = newh; //h 为新的跳表的head节点 idx = idxs[level = oldLevel]; //新增的层中,包含Head和idxs[max]两个节点,其指向关系已经确定,而oldLevel中,还没有设置idxs[level]的前置节点,因此idx = idxs[level = oldLevel],说明需要从此层开始至最底层,设置好idxs[level]的前置节点,下面的代码splice完成该功能。 break; } } } // find insertion points and splice in splice: for (int insertionLevel = level;;) { 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);//key 根当前node.key比较 if (n.value == null) { if (!q.unlink(r)) break; r = q.right; continue; } if (c > 0) { //继续往右查找 q = r; r = r.right; continue; } } if (j == insertionLevel) { if (!q.link(r, t)) // 把节点t插入到q和r之间,t即新增的节点idx[level] break; // restart if (t.node.value == null) { findNode(key); break splice; } if (--insertionLevel == 0)//层数往下,如果已到最底层,则退出,最底层的节点值在之前的代码中已经完成插入。 break splice; } if (--j >= insertionLevel && j < level) t = t.down; //t值更新,t即新增的节点idx[level] q = q.down; r = q.right; } } } return null; }
删除一个节点:
/** * Main deletion method. Locates node, nulls value, appends a * deletion marker, unlinks predecessor, removes associated index * nodes, and possibly reduces head index level. * * Index nodes are cleared out simply by calling findPredecessor. * which unlinks indexes to deleted nodes found along path to key, * which will include the indexes to this node. This is done * unconditionally. We can't check beforehand whether there are * index nodes because it might be the case that some or all * indexes hadn't been inserted yet for this node during initial * search for it, and we'd like to ensure lack of garbage * retention, so must call to be sure. * * @param key the key * @param value if non-null, the value that must be * associated with key * @return the node, or null if not found */ final V doRemove(Object key, Object value) { 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) break outer; if (c > 0) { b = n; n = f; continue; } if (value != null && !value.equals(v)) //如果value不相等,退出 break outer; if (!n.casValue(v, null)) //无限循环,直至设置节点的值为null成功, break; //之前已经把当前节点值设为null,之后的删除操作分两步:1,在n和n.next间插入一个删除标记节点marker; // 2,设置b.next为f;这是由两个原子操作共同完成,如果都正常完成,则直接返回;如果有其中一步失败,则调用findNode(key)来继续完成删除null节点的操作; if (!n.appendMarker(f) || !b.casNext(n, f)) findNode(key); // retry via findNode else { . findPredecessor(key, cmp); // clean index if (head.right == null) tryReduceLevel(); //最上面三层都无索引节点,则把最上面一层的索引删除。 } @SuppressWarnings("unchecked") V vv = (V)v; return vv; } } return null; } /** * 添加一个删除标记节点,设置当前节点的next节点为new Node(f),该新增节点的value值为当前节点f.value=f; * Tries to append a deletion marker to this node. * @param f the assumed current successor of this node * @return true if successful */ boolean appendMarker(Node<K,V> f) { return casNext(f, new Node<K,V>(f)); } Node(Node<K,V> next) { this.key = null; this.value = this; this.next = next; } /** * compareAndSet next field */ boolean casNext(Node<K,V> cmp, Node<K,V> val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } /** * 继续完成删除节点过程: * Helps out a deletion by appending marker or unlinking from * predecessor. This is called during traversals when value * field seen to be null. * @param b predecessor * @param f successor */ void helpDelete(Node<K,V> b, Node<K,V> f) { /* * Rechecking links and then doing only one of the * help-out stages per call tends to minimize CAS * interference among helping threads. */ if (f == next && this == b.next) { if (f == null || f.value != f) // not already marked 判断是否为marker节点(f.value=f) casNext(f, new Node<K,V>(f)); else b.casNext(this, f.next); } }
通过源代码具体分析其删除步骤:
JDK的跳表源码分析的更多相关文章
- JDK动态代理实现源码分析
JDK动态代理实现方式 在Spring框架中经典的AOP就是通过动态代理来实现的,Spring分别采用了JDK的动态代理和Cglib动态代理,本文就来分析一下JDK是如何实现动态代理的. 在分析源码之 ...
- Redis学习之zskiplist跳跃表源码分析
跳跃表的定义 跳跃表是一种有序数据结构,它通过在每个结点中维持多个指向其他结点的指针,从而达到快速访问其他结点的目的 跳跃表的结构 关于跳跃表的学习请参考:https://www.jianshu.co ...
- JDK 1.6 HashMap 源码分析
前言 前段时间研究了一下JDK 1.6 的 HashMap 源码,把部份重要的方法分析一下,当然HashMap中还有一些值得研究得就交给读者了,如有不正确之处还望留言指正. 准备 需要熟悉数组 ...
- JDK 之 Arrays.asList - 源码分析
Arrays工具类提供了一个方法asList, 使用该方法可以将一个变长参数或者数组转换成List . 其源代码如下: @SafeVarargs public static <T> Lis ...
- JDK Collection 源码分析(2)—— List
JDK List源码分析 List接口定义了有序集合(序列).在Collection的基础上,增加了可以通过下标索引访问,以及线性查找等功能. 整体类结构 1.AbstractList 该类作为L ...
- JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable
JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...
- JDK源码分析(12)之 ConcurrentHashMap 详解
本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 Ha ...
- JDK(五)JDK1.8源码分析【集合】HashMap
本文转载自无始无终,原文连接 HashMap 在 JDK 1.8 后新增的红黑树结构 传统 HashMap 的缺点 JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也 ...
- C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库
C# DateTime的11种构造函数 别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...
随机推荐
- 移动端HTML5实现文件上传
PC端上传文件多半用插件,引入flash都没关系,但是移动端要是还用各种冗余的插件估计得被喷死,项目里面需要做图片上传的功能,既然H5已经有相关的接口且兼容性良好,当然优先考虑用H5来实现. 用的技术 ...
- PHP读取Excel类文件
想要使用PHP读取Excel文件必然要用到PHPExcel开源类库,网上资源应该挺多的.但是每一种的操作必然都是不同的,可原理应该都是大同小异. 这个文件夹里包含的就是PHPExcel类文件,在外面还 ...
- CSS 伪类(下)结构性伪类\UI伪类\动态伪类和其他伪类 valid check enable child required link visit
伪类选择器汇总伪类选择器有4种, 结构性伪类\UI伪类\动态伪类和其他伪类. 具体如下 结构性伪类选择器结构性伪类选择器它能够根据元素在文档中的位置选择元素, 这类元素都有个前缀":&q ...
- Unable to update index for central http://repo1.maven.org/maven2/ 解决方法
不知道什么原因 MyEclipse(eclipse) 中的 maven 插件突然不能用了,修改 pom.xml 无任何反应 控制台报 Unable to update index for centra ...
- 微服务实战(三):以MySQL为例,从原理上理解那些所谓的数据库军规
原文链接:微服务化的数据库设计与读写分离(来源:刘超的通俗云计算) 数据库永远是应用最关键的一环,同时越到高并发阶段,数据库往往成为瓶颈,如果数据库表和索引不在一开始就进行良好的设计,则后期数据库横向 ...
- No toolchains found in the NDK toolchains folder for ABI with prefix: arm-linux-androideabi
产生背景最近把Android Studio更新到3.0,更新之后出现了build错误:No toolchains found in the NDK toolchains folder for ABI ...
- MUI框架-10-MUI 数据交互-跳转详情页面
MUI框架-10-MUI 数据交互-跳转详情页面 上一篇介绍了如何实现数据交互,给别人的 API 发送 ajax 请求,我们得到数据,再使用 art-template 模板引擎拼接 HTML,最终实现 ...
- ISO14971-2007阅读
1.什么是风险? 风险的概念,公认的组成有两部分: 损害发生的概率 损害的后果,即损坏的严重性 2.风险管理的适用范围? 适用于医疗器械生命周期所有阶段 不适用于临床判断 不要求具体的质量体系,但14 ...
- Struts 2 官方文档中文版
最近正在学 Struts 2 ,咱英文水平不行啊,找到了<Struts 2 官方文档中文版>,虽然翻译的不全,有总比没有强. 地址:https://cwiki.apache.org/con ...
- aspnetcore 认证相关类简要说明三
今天我们再来了解一个很重要的接口IAuthenticationService的实现类AuthenticationService: public class AuthenticationService ...