本文首发于一世流云专栏: https://segmentfault.com/blog...
在正式讲ConcurrentSkipListMap以前,咱们先来看下ConcurrentSkipListMap的类继承图:java
咱们知道,通常的Map都是无序的,也就是只能经过键的hash值进行定位。JDK为了实现有序的Map,提供了一个SortedMap接口,SortedMap提供了一些根据键范围进行查找的功能,好比返回整个Map中 key最小/大的键、返回某个范围内的子Map视图等等。node
为了进一步对有序Map进行加强,JDK又引入了NavigableMap接口,该接口进一步扩展了SortedMap的功能,提供了根据指定Key返回最接近项、按升序/降序返回全部键的视图等功能。算法
同时,也提供了一个基于NavigableMap的实现类——TreeMap,TreeMap底层基于红黑树设计,是一种有序的Map。关于TreeMap和NavigableMap,本文不做赘述,读者能够查看Oracle的官方文档:https://docs.oracle.com/javas...。segmentfault
JDK1.6时,为了对高并发环境下的有序Map提供更好的支持,J.U.C新增了一个ConcurrentNavigableMap接口,ConcurrentNavigableMap很简单,它同时实现了NavigableMap和ConcurrentMap接口:api
ConcurrentNavigableMap接口提供的功能也和NavigableMap几乎彻底一致,不少方法仅仅是返回的类型不一样:数组
J.U.C提供了基于ConcurrentNavigableMap接口的一个实现——ConcurrentSkipListMap
。ConcurrentSkipListMap能够当作是并发版本的TreeMap,可是和TreeMap不一样是,ConcurrentSkipListMap并非基于红黑树实现的,其底层是一种相似跳表(Skip List)的结构。安全
Skip List(如下简称跳表),是一种相似链表的数据结构,其查询/插入/删除的时间复杂度都是O(logn)
。数据结构
咱们知道,一般意义上的链表是不能支持随机访问的(经过索引快速定位),其查找的时间复杂度是O(n)
,而数组这一可支持随机访问的数据结构,虽然查找很快,可是插入/删除元素却须要移动插入点后的全部元素,时间复杂度为O(n)
。并发
为了解决这一问题,引入了树结构,树的增删改查效率比较平均,一棵平衡二叉树(AVL)的增删改查效率通常为O(logn)
,好比工业上经常使用红黑树做为AVL的一种实现。oracle
可是,AVL的实现通常都比较复杂,插入/删除元素可能涉及对整个树结构的修改,特别是并发环境下,一般须要全局锁来保证AVL的线程安全,因而又出现了一种相似链表的数据结构——跳表。
在讲Skip List以前,咱们先来看下传统的单链表:
上图的单链表中(省去告终点之间的连接),当想查找七、1五、46这三个元素时,必须从头指针head开始,遍历整个单链表,其查找复杂度很低,为O(n)
。
来看下Skip List的数据结构是什么样的:
上图是Skip List一种可能的结构,它分了2层,假设咱们要查找“15”这个元素,那么整个步骤以下:
上述整个查找路径以下图标黄部分所示:
同理,若是要查找“46”这个元素,则整个查找路径以下图标黄部分所示:
上面就是跳跃表的基本思想了,每一个结点不只仅只包含指向下一个结点的指针,可能还包含不少个其它指向后续结点的指针。而且,一个结点自己能够当作是一个链表(自上向下连接)。这样就能够跳过一些没必要要的结点,从而加快查找、删除等操做,这实际上是一种“空间换时间”的算法设计思想。
那么一个结点能够包含多少层呢? 好比,Skip List也多是下面这种包含3层的结构(在一个3层Skip List中查找元素“46”):
层数是根据一种随机算法获得的,为了避免让层数过大,还会有一个最大层数MAX_LEVEL限制,随机算法生成的层数不得大于该值。后面讲ConcurrentSkipListMap时,咱们会具体分析。
以上就是Skip List的基本思想了,总结起来,有如下几点:
介绍完了跳表,再来看ConcurrentSkipListMap的内部结构就容易得多了:
ConcurrentSkipListMap内部一共定义了3种不一样类型的结点,元素的增删改查都从最上层的head指针指向的结点开始:
public class ConcurrentSkipListMap2<K, V> extends AbstractMap<K, V> implements ConcurrentNavigableMap<K, V>, Cloneable, Serializable { /** * 最底层链表的头指针BASE_HEADER */ private static final Object BASE_HEADER = new Object(); /** * 最上层链表的头指针head */ private transient volatile HeadIndex<K, V> head; /* ---------------- 普通结点Node定义 -------------- */ static final class Node<K, V> { final K key; volatile Object value; volatile Node<K, V> next; // ... } /* ---------------- 索引结点Index定义 -------------- */ static class Index<K, V> { final Node<K, V> node; // node指向最底层链表的Node结点 final Index<K, V> down; // down指向下层Index结点 volatile Index<K, V> right; // right指向右边的Index结点 // ... } /* ---------------- 头索引结点HeadIndex -------------- */ static final class HeadIndex<K, V> extends Index<K, V> { final int level; // 层级 // ... } }
咱们来看下这3类结点的具体定义。
普通结点:Node
普通结点——Node,也就是ConcurrentSkipListMap最底层链表中的结点,保存着实际的键值对,若是单独看底层链,其实就是一个按照Key有序排列的单链表:
static final class Node<K, V> { final K key; volatile Object value; volatile Node<K, V> next; /** * 正常结点. */ Node(K key, Object value, Node<K, V> next) { this.key = key; this.value = value; this.next = next; } /** * 标记结点. */ Node(Node<K, V> next) { this.key = null; this.value = this; this.next = next; } /** * CAS更新结点的value */ boolean casValue(Object cmp, Object val) { return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val); } /** * CAS更新结点的next */ boolean casNext(Node<K, V> cmp, Node<K, V> val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } /** * 判断当前结点是否为[标记结点] */ boolean isMarker() { return value == this; } /** * 判断当前结点是不是最底层链表的头结点 */ boolean isBaseHeader() { return value == BASE_HEADER; } /** * 在当前结点后面插入一个标记结点. * * @param f 当前结点的后继结点 * @return true 插入成功 */ boolean appendMarker(Node<K, V> f) { return casNext(f, new Node<K, V>(f)); } /** * 辅助删除结点方法. * * @param b 当前结点的前驱结点 * @param f 当前结点的后继结点 */ void helpDelete(Node<K, V> b, Node<K, V> f) { /* * 从新检查一遍结点位置 * 确保b和f分别为当前结点的前驱/后继 */ if (f == next && this == b.next) { if (f == null || f.value != f) // f为null或非标记结点 casNext(f, new Node<K, V>(f)); else // 删除当前结点 b.casNext(this, f.next); } } /** * 返回结点的value值. * * @return 标记结点或最底层头结点,直接返回null */ V getValidValue() { Object v = value; if (v == this || v == BASE_HEADER) // 标记结点或最底层头结点,直接返回null return null; V vv = (V) v; return vv; } /** * 返回当前结点的一个Immutable快照. */ AbstractMap.SimpleImmutableEntry<K, V> createSnapshot() { Object v = value; if (v == null || v == this || v == BASE_HEADER) return null; V vv = (V) v; return new AbstractMap.SimpleImmutableEntry<K, V>(key, vv); } // UNSAFE mechanics private static final sun.misc.Unsafe UNSAFE; private static final long valueOffset; private static final long nextOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = Node.class; valueOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("value")); nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } }
索引结点:Index
Index结点是除底层链外,其他各层链表中的非头结点(见示意图中的蓝色结点)。每一个Index结点包含3个指针:down
、right
、node
。
down和right指针分别指向下层结点和后继结点,node指针指向其最底部的node结点。
static class Index<K, V> { final Node<K, V> node; // node指向最底层链表的Node结点 final Index<K, V> down; // down指向下层Index结点 volatile Index<K, V> right; // right指向右边的Index结点 Index(Node<K, V> node, Index<K, V> down, Index<K, V> right) { this.node = node; this.down = down; this.right = right; } /** * CAS更新右边的Index结点 * * @param cmp 当前结点的右结点 * @param val 但愿更新的结点 */ final boolean casRight(Index<K, V> cmp, Index<K, V> val) { return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val); } /** * 判断Node结点是否已经删除. */ final boolean indexesDeletedNode() { return node.value == null; } /** * CAS插入一个右边结点newSucc. * * @param succ 当前的后继结点 * @param newSucc 新的后继结点 */ final boolean link(Index<K, V> succ, Index<K, V> newSucc) { Node<K, V> n = node; newSucc.right = succ; return n.value != null && casRight(succ, newSucc); } /** * 跳过当前结点的后继结点. * * @param succ 当前的后继结点 */ final boolean unlink(Index<K, V> succ) { return node.value != null && casRight(succ, succ.right); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long rightOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = Index.class; rightOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("right")); } catch (Exception e) { throw new Error(e); } } }
头索引结点:HeadIndex
HeadIndex结点是各层链表的头结点,它是Index类的子类,惟一的区别是增长了一个level
字段,用于表示当前链表的级别,越往上层,level值越大。
static final class HeadIndex<K, V> extends Index<K, V> { final int level; // 层级 HeadIndex(Node<K, V> node, Index<K, V> down, Index<K, V> right, int level) { super(node, down, right); this.level = level; } }
ConcurrentSkipListMap一共定义了4种构造器:
空构造器
/** * 构造一个新的空Map. */ public ConcurrentSkipListMap() { this.comparator = null; initialize(); }
指定比较器的构造器
/** * 构造一个新的空Map. * 并指定比较器. */ public ConcurrentSkipListMap(Comparator<? super K> comparator) { this.comparator = comparator; initialize(); }
从给定Map构建的构造器
/** * 从已给定的Map构造一个新Map. */ public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) { this.comparator = null; initialize(); putAll(m); }
从给定SortedMap构建的构造器
/** * 从已给定的SortedMap构造一个新Map. * 而且Key的顺序与原来保持一致. */ public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) { this.comparator = m.comparator(); initialize(); buildFromSorted(m); }
注:ConcurrentSkipListMap会基于比较器——Comparator ,来进行键Key的比较,若是构造时未指定Comparator ,那么就会按照Key的天然顺序进行比较,所谓Key的天然顺序是指key实现Comparable接口。
上述全部构造器都调用了initialize方法:
private void initialize() { keySet = null; entrySet = null; values = null; descendingMap = null; head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null),null, null, 1); }
initialize方法将一些字段置初始化null,而后将head指针指向新建立的HeadIndex结点。初始化完成后,ConcurrentSkipListMap的结构以下:
其中,head和BASE_HEADER都是ConcurrentSkipListMap的字段:
/** * 最底层链表的头指针BASE_HEADER */ private static final Object BASE_HEADER = new Object(); /** * 最上层链表的头指针head */ private transient volatile HeadIndex<K, V> head;
put操做自己很简单,须要注意的是ConcurrentSkipListMap在插入键值对时,Key和Value都不能为null:
/** * 插入键值对. * * @param key 键 * @param value 值 * @return 若是key存在,返回旧value值;不然返回null */ public V put(K key, V value) { if (value == null) // ConcurrentSkipListMap的Value不能为null throw new NullPointerException(); return doPut(key, value, false); }
上述方法内部调用了doPut来作实际的插入操做:
/** * 插入键值对. * * @param onlyIfAbsent true: 仅当Key不存在时才进行插入 */ private V doPut(K key, V value, boolean onlyIfAbsent) { Node<K, V> z; // z指向待添加的Node结点 if (key == null) // ConcurrentSkipListMap的Key不能为null throw new NullPointerException(); Comparator<? super K> cmp = comparator; outer: for (; ; ) { // b是“是小于且最接近给定key”的Node结点(或底层链表头结点) for (Node<K, V> b = findPredecessor(key, cmp), n = b.next; ; ) { if (n != null) { // b存在后驱结点: b -> n -> f Object v; int c; Node<K, V> f = n.next; // f指向b的后驱的后驱 if (n != b.next) // 存在并发修改,放弃并重试 break; if ((v = n.value) == null) { // n为标记删除结点 n.helpDelete(b, f); break; } if (b.value == null || v == n) // b为标记删除结点 break; if ((c = cpr(cmp, key, n.key)) > 0) { // 向后遍历,找到第一个大于key的结点 b = n; n = f; continue; } if (c == 0) { // 存在Key相同的结点 if (onlyIfAbsent || n.casValue(v, value)) { V vv = (V) v; return vv; } break; // CAS更新失败,则重试 } } z = new Node<K, V>(key, value, n); if (!b.casNext(n, z)) // 尝试插入z结点: b -> z -> n break; // CAS插入失败,则重试 break outer; // 跳出最外层循环 } } int rnd = ThreadLocalRandom.nextSecondarySeed(); // 生成一个随机数种子 if ((rnd & 0x80000001) == 0) { // 为true表示须要增长层级 /** * 如下方法用于建立新层级 */ int level = 1, max; while (((rnd >>>= 1) & 1) != 0) // level表示新的层级,经过下面这个while循环能够确认新的层级数 ++level; Index<K, V> idx = null; HeadIndex<K, V> h = head; if (level <= (max = h.level)) { // CASE1: 新层级level没有超过最大层级head.level(head指针指向最高层) // 以“头插法”建立level个Index结点,idx最终指向最高层的Index结点 for (int i = 1; i <= level; ++i) idx = new Index<K, V>(z, idx, null); } else { // CASE2: 新层级level超过了最大层级head.level level = max + 1; // 重置level为最大层级+1 // 生成一个Index结点数组,idxs[0]不会使用 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); // 生成新的HeadIndex结点 for (; ; ) { h = head; int oldLevel = h.level; // 原最大层级 if (level <= oldLevel) break; HeadIndex<K, V> newh = h; Node<K, V> oldbase = h.node; // oldbase指向最底层链表的头结点 for (int j = oldLevel + 1; j <= level; ++j) newh = new HeadIndex<K, V>(oldbase, newh, idxs[j], j); if (casHead(h, newh)) { h = newh; idx = idxs[level = oldLevel]; break; } } } /** * 如下方法用于连接新层级的各个HeadIndex和Index结点 */ splice: for (int insertionLevel = level; ; ) { // 此时level为oldLevel,即原最大层级 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; int c = cpr(cmp, key, n.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)) // 在q和r之间插入t,即从 q -> r 变成 q -> t -> r break; if (t.node.value == null) { findNode(key); break splice; } if (--insertionLevel == 0) break splice; } if (--j >= insertionLevel && j < level) t = t.down; q = q.down; r = q.right; } } } return null; }
咱们先不急着看doPut方法,而是看下其内部的findPredecessor
方法,findPredecessor用于查找“小于且最接近给定key”的Node结点,而且这个Node结点必须有上层结点:
/** * 返回“小于且最接近给定key”的数据结点. * 若是不存在这样的数据结点,则返回底层链表的头结点. * * @param key 待查找的键 */ private Node<K, V> findPredecessor(Object key, Comparator<? super K> cmp) { if (key == null) throw new NullPointerException(); /** * 从最上层开始,往右下方向查找 */ for (; ; ) { for (Index<K, V> q = head, r = q.right, d; ; ) { // 从最顶层的head结点开始查找 if (r != null) { // 存在右结点 Node<K, V> n = r.node; K k = n.key; if (n.value == null) { // 处理结点”懒删除“的状况 if (!q.unlink(r)) break; r = q.right; continue; } if (cpr(cmp, key, k) > 0) { // key大于k,继续向右查找 q = r; r = r.right; continue; } } //已经到了level1的层 if ((d = q.down) == null) // 不存在下结点,说明q已是level1链表中的结点了 return q.node; // 直接返回对应的Node结点 // 转到下一层,继续查找(level-1层) q = d; r = d.right; } } }
看代码不太直观,咱们仍是看下面这个图:
上图中,假设要查找的Key为72,则步骤以下:
注意:若是咱们要查找key为59的Node结点,返回的不是Key为45的结点,而是key为23的结点。读者能够本身在纸上比划下。
回到doPut方法,假设如今待插入的Key为3,则当执行完下面这段代码后,ConcurrentSkipListMap的结构以下:
/** * 插入键值对. * * @param onlyIfAbsent true: 仅当Key不存在时才进行插入 */ private V doPut(K key, V value, boolean onlyIfAbsent) { Node<K, V> z; // z指向待添加的Node结点 if (key == null) // ConcurrentSkipListMap的Key不能为null throw new NullPointerException(); Comparator<? super K> cmp = comparator; outer: for (; ; ) { // b是“是小于且最接近给定key”的Node结点(或底层链表头结点) for (Node<K, V> b = findPredecessor(key, cmp), n = b.next; ; ) { if (n != null) { // b存在后驱结点: b -> n -> f Object v; int c; Node<K, V> f = n.next; // f指向b的后驱的后驱 if (n != b.next) // 存在并发修改,放弃并重试 break; if ((v = n.value) == null) { // n为标记删除结点 n.helpDelete(b, f); break; } if (b.value == null || v == n) // b为标记删除结点 break; if ((c = cpr(cmp, key, n.key)) > 0) { // 向后遍历,找到第一个大于key的结点 b = n; n = f; continue; } if (c == 0) { // 存在Key相同的结点 if (onlyIfAbsent || n.casValue(v, value)) { V vv = (V) v; return vv; } break; // CAS更新失败,则重试 } } z = new Node<K, V>(key, value, n); if (!b.casNext(n, z)) // 尝试插入z结点: b -> z -> n break; // CAS插入失败,则重试 break outer; // 跳出最外层循环 } // ... } }
上面是doPut中的第一个循环,做用就是找到底层链表的插入点,而后插入结点(在查找过程当中可能会删除一些已标记的删除结点)。
插入完成后,doPut方法并没结束,咱们以前说过ConcurrentSkipListMap的分层数是经过一个随机数生成算法来肯定,doPut的后半段,就是这个做用:判断是否须要增长层级,若是须要就在各层级中插入对应的Index结点。
/** * 插入键值对. * * @param onlyIfAbsent true: 仅当Key不存在时才进行插入 */ private V doPut(K key, V value, boolean onlyIfAbsent) { // ... int rnd = ThreadLocalRandom.nextSecondarySeed(); // 生成一个随机数种子 if ((rnd & 0x80000001) == 0) { // 为true表示须要增长层级 /** * 如下方法用于建立新层级 */ int level = 1, max; while (((rnd >>>= 1) & 1) != 0) // level表示新的层级,经过下面这个while循环能够确认新的层级数 ++level; Index<K, V> idx = null; HeadIndex<K, V> h = head; if (level <= (max = h.level)) { // CASE1: 新层级level没有超过最大层级head.level(head指针指向最高层) // 以“头插法”建立level个Index结点,idx最终指向最高层的Index结点 for (int i = 1; i <= level; ++i) idx = new Index<K, V>(z, idx, null); } else { // CASE2: 新层级level超过了最大层级head.level level = max + 1; // 重置level为最大层级+1 // 生成一个Index结点数组,idxs[0]不会使用 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); // 生成新的HeadIndex结点 for (; ; ) { h = head; int oldLevel = h.level; // 原最大层级 if (level <= oldLevel) break; HeadIndex<K, V> newh = h; Node<K, V> oldbase = h.node; // oldbase指向最底层链表的头结点 for (int j = oldLevel + 1; j <= level; ++j) newh = new HeadIndex<K, V>(oldbase, newh, idxs[j], j); if (casHead(h, newh)) { h = newh; idx = idxs[level = oldLevel]; break; } } } /** * 如下方法用于连接新层级的各个HeadIndex和Index结点 */ splice: for (int insertionLevel = level; ; ) { // 此时level为oldLevel,即原最大层级 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; int c = cpr(cmp, key, n.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)) // 在q和r之间插入t,即从 q -> r 变成 q -> t -> r break; if (t.node.value == null) { findNode(key); break splice; } if (--insertionLevel == 0) break splice; } if (--j >= insertionLevel && j < level) t = t.down; q = q.down; r = q.right; } } } return null; }
最终ConcurrentSkipListMap的结构以下所示:
ConcurrentSkipListMap在删除键值对时,不会当即执行删除,而是经过引入“标记结点”,以“懒删除”的方式进行,以提升并发效率。
public V remove(Object key) { return doRemove(key, null); }
remove方法很简单,内部调用了doRemove方法:
final V doRemove(Object key, Object value) { if (key == null) throw new NullPointerException(); Comparator<? super K> cmp = comparator; outer: for (; ; ) { // b指向“小于且最接近给定key”的Node结点(或底层链表头结点) for (Node<K, V> b = findPredecessor(key, cmp), n = b.next; ; ) { // b -> n Object v; int c; if (n == null) break outer; Node<K, V> f = n.next; // b -> n -> f if (n != b.next) // 一致性判断 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; } // 此时n指向查到的结点 if (value != null && !value.equals(v)) break outer; if (!n.casValue(v, null)) // 更新查找到的结点的value为null break; // 在n和f之间添加标记结点,并将b直接指向f if (!n.appendMarker(f) || !b.casNext(n, f)) // n -> marker -> f findNode(key); // retry via findNode else { findPredecessor(key, cmp); // 删除Index结点 if (head.right == null) // 减小层级 tryReduceLevel(); } V vv = (V) v; return vv; } } return null; }
仍是经过示例来理解上述代码,假设如今要删除Key==23的结点,删除前ConcurrentSkipListMap的结构以下:
doRemove方法首先会找到待删除的结点,在它和后继结点之间插入一个value为null的标记结点(以下图中的绿色结点),而后改变其前驱结点的指向:
最后,doRemove会从新调用一遍findPredecessor方法,解除被删除结点上的Index结点之间的引用:
这样Key==23的结点其实就被孤立,再后续查找或插入过程当中,会被彻底清除或被GC回收。
最后,咱们来看下ConcurrentSkipListMap的查找操做——get方法。
public V get(Object key) { return doGet(key); }
内部调用了doGet方法:
private V doGet(Object key) { if (key == null) throw new NullPointerException(); Comparator<? super K> cmp = comparator; outer: for (; ; ) { // b指向“小于且最接近给定key”的Node结点(或底层链表头结点) 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; // b -> n -> f if (n != b.next) 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) { V vv = (V) v; return vv; } if (c < 0) break outer; b = n; n = f; } } return null; }
doGet方法很是简单:
首先找到“小于且最接近给定key”的Node结点,而后用了三个指针:b -> n -> f,
n用于定位最终查找的Key,而后顺着链表一步步向下查,好比查找KEY==45,则最终三个指针的位置以下: