生活
目标定下来以后就不要去变,只要确定是对的,总可以到达。
二分查找
二分查找要求有序性,为了保障可以随机访问,因此会把数据保存在连续的内存中,在查询的时候效率高,但是在增加和删除时需要大量移动元素以保证有序,所以效率不高。
如果需要快速的二分查找,又要兼顾删除增加元素的效率,可以考虑使用二叉查找树,但是二叉树在极端情况下会变成一个链表,使原本O(log n)的时间复杂度,变成O(n)。
于是就出现了平衡二叉树,例如AVL树,红黑树,但是平衡二叉树比较难理解,尤其是红黑树的左旋右旋删除操作。
于是乎出现了跳跃表结构。
今天就来看看这个跳跃表是个啥?
什么是跳跃表?
先简单的通过图示来看下,什么是跳跃表?
传递的链表都是单链表结构,要向一个单链表中增加删除查询修改一个节点的时间复杂度都是O(n),
跳跃表其实也是链表,只是在链表的基础上加上了一系列index,使之高效。
如上图所示就是一个跳跃表,每个节点都可以存在多个指向其他节点的索引。他可以先通过最上面的索引来查找数据,过滤掉一半的节点,他的查找效率是O(n/2)。
举个例子 查找25,
先比较6,在比较9、17、21、26 ,然后可知数据再21和26之间,随之就找到了25.
跳跃表如何查询?
ok,具体的查询,下面也来看下图示。
这是查找19的图示。
每一个节点都不止包含指向下一个节点的指针,也可以包含多个指向其他节点的指针,这样就可以跳过一些没有必要的结果,从而提高查询的效率。
至于每个节点包含多少点后继节点个数,其实是通过随机生成的,从而形成了跳跃表。
因为是随机的,所以跳跃表是一种概率均衡而不是强制均衡。
在Redis/leveldb有用到。
ConcurrentSkipListMap 数据结构
下面来看下jdk1.8里的跳跃表:ConcurrentSkipListMap
这个玩意 1.7有所不同,但是基本实现是一致的,代码里一些细节稍稍不一样。
先来看下他的数据结构,
//节点对象
static final class Node<K,V> {
final K key;
volatile Object value;
//下一个节点
volatile Node<K,V> next;
}
//索引对象
static class Index<K,V> {
//节点
final Node<K,V> node;
//指向该节点下一个层级的索引
final Index<K,V> down;
//指向右边的索引,即一个节点的索引
volatile Index<K,V> right;
}
//多个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 成员
//主要就是这两个
//头索引
private transient volatile HeadIndex<K,V> head;
/**
* The comparator used to maintain order in this map, or null if
* using natural ordering. (Non-private to simplify access in
* nested classes.)
* @serial
*/
//比较器
final Comparator<? super K> comparator;
ConcurrentSkipListMap 构造器
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
}
public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
this.comparator = null;
initialize();
putAll(m);
}
//初始化一个头结点,注意level是1,就是第一层,key value都是空
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);
}
ConcurrentSkipListMap put
来看下跳跃表增加数据是怎么做的?
核心方法是doPut()
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;;) {
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;
}
// 0,说明本身有这个key,根据策略看是否覆盖
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);
//设置自己为我的前驱的后驱
if (!b.casNext(n, z))
break; // restart if lost race to append to b
break outer;
}
}
// 随机数,跟level有关啊
int rnd = ThreadLocalRandom.nextSecondarySeed();
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0)
++level;
Index<K,V> idx = null;
HeadIndex<K,V> h = head;
//如果level比现在的level小
if (level <= (max = h.level)) {
//那就直接创建Index,并一级一级把自己down index设置好
for (int i = 1; i <= level; ++i)
idx = new Index<K,V>(z, idx, null);
}
else { // try to grow by one level
//如果这个level大于max,那就设置他是max+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];
//创建自己的index
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index<K,V>(z, idx, null);
for (;;) {
h = head;
int oldLevel = h.level;
//如果level小于等于原最大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)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
//并替换头index
if (casHead(h, newh)) {
h = newh;
idx = idxs[level = oldLevel];
break;
}
}
}
//保存新跳表的跳级,并且把right索引设置好。
// 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);
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))
break; // restart
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;
}
来看下寻找前驱节点的方法:
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) {
//中间遇到值为空的,就删除索引。注意前面的helpDelete是使node出链表。这里是删索引,不一样的,,刚开始这里看了半天
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;
}
}
//否则往下找,如果下面已经没有了,,那就是这个节点了
if ((d = q.down) == null)
return q.node;
q = d;
r = d.right;
}
}
}
来看下unlink的方法做了什么
//就是跳过这个节点,然后把right index往后指
final boolean unlink(Index<K,V> succ) {
return node.value != null && casRight(succ, succ.right);
}
ConcurrentSkipListMap get
这里来看下get方法
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
//找前驱或者自己这个节点,其实这个get就是想找自己这个节点,如果没有找到的是前驱
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
//大于0的情况就一直往后找,直接null
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;
//找到就return
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
// c<0说明没有这个节点直接break
if (c < 0)
break outer;
b = n;
n = f;
}
}
return null;
}
ConcurrentSkipListMap remove
最后来看下如何删除数据?
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))
break outer;
if (!n.casValue(v, null))
break;
//标记他可以删除,并且设置自己的前驱节点 指向自己的后驱节点
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;
}
来看下appendMarker实际做了什么?
boolean appendMarker(Node<K,V> f) {
return casNext(f, new Node<K,V>(f));
}
//设置自己的next的Node 里的value是 现在的下一个节点对象。。
有点绕,不知道为什么这么设计