1.概述
跳表(跳跃表)是一种数据结构,改进自链表,用于存储有序的数据,跳跃表通过空间换时间的方法来提高数据检索的速度。
需要排序的东西不需要持久化或者为了更高的效率,是直接在内存中进行排序,那么使用到的数据结构就是jdk中的ConcurrentSkipListMap(支持同步)或者TreeMap(不支持同步,性能更好)
2.场景
通过上面的介绍,我们了解到跳跃表的应用场景大概是这样的:
- 有序
- 频繁插入删除
- 频繁的查找
对于有序来说,数组和普通链表都可以通过排序算法来实现,在排序复杂度上不相上下。链表在插入和删除上性能较好,数组在查找上性能较好,所以都不能满足我们的要求。跳跃表则是在插入删除和查找的性能上做了折中,复杂度为log(n)。
跳表结构如下所示:

为了更好的支持插入和删除,所以采用链表的形式,可以看到图片中最下面一行是一个有序的链表。但如果只是一个单一的链表,查找时复杂度为O(n),性能太差。如何优化呢?
3.节点
在有序数组中,我们查找时用的是二分查找,一次查找可以排除一半元素的遍历。在数组中之所以可以用二分查找,是因为我们能够快速的以O(1)的复杂度定位到中间的位置,但是链表只能是O(N)。所以跳跃表采取空间换时间的方式,既然你找不到中间点,或者三分之一点等中间位置,那么我可以通过多增加一个节点来指向中间位置,这样你也能够快速的定位到中间的位置,然后一定程度的减少你遍历元素的个数,提高效率。图中有多层,相邻的两层,采用的都是这样的思想。
3.节点
通过上图,可以发现有两种节点类型,第一种是最下层的,与普通链表相似,第二种是除了最后一层以外的其他索引层节点,有两个指针。
但是在jdk源码中还存在一种节点,是索引层的头节点,还维护了其层数信息
但是在jdk源码中还存在一种节点,是索引层的头节点,还维护了其层数信息
// Node是最底层的链表的节点,包括键值对和指向下一个节点的指针
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;
}
// ...配套method
}
// 索引节点结构
// 存储了两个指针,分别指向右边和下边的节点
// 索引节点的value为链表节点
static class Index<K,V> {
final Node<K,V> node;
final Index<K,V> down;
volatile Index<K,V> right;
Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
this.node = node;
this.down = down;
this.right = right;
}
// ...配套method
}
// 索引层的头节点结构
// 在索引节点的基础上添加了表示层数的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;
}
}
4.构造函数
// Compartor接口用来指定key的排序规则
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
}
// 还有两个传入map和sortedMap的构造函数
private void initialize() {
keySet = null;// 内部类
entrySet = null;// 内部类
values = null;// 内部类
descendingMap = null;// 内部类
// private static final Object BASE_HEADER = new Object();
// 从注释给出的图来看,这个head应该是一直处于第一层的头节点
head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
null, null, 1);
}