生活
目标定下来以后就不要去变,只要确定是对的,总可以到达。
二分查找
二分查找要求有序性,为了保障可以随机访问,因此会把数据保存在连续的内存中,在查询的时候效率高,但是在增加和删除时需要大量移动元素以保证有序,所以效率不高。
如果需要快速的二分查找,又要兼顾删除增加元素的效率,可以考虑使用二叉查找树,但是二叉树在极端情况下会变成一个链表,使原本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&l