数据结构-跳表(基于JAVA的实现)

数据结构-跳表(基于JAVA的实现)

  • 跳表的原理与特点
    • 跳跃表应用场景分析
    • 跳跃表的JAVA应用
      • 使用样例
      • 源码分析
        • ConcurrentSkipListMap
        • ConcurrentSkipListSet

跳表的原理与特点

跳表实质就是一种可以进行二分查找的有序链表,跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。解决了链表查询慢的问题,但会占有更多的内存,是一种以空间换内存的数据结构。

查询任意数据的时间复杂度O(logn)
插入数据的时间复杂度O(logn)
空间复杂度:O(n)

数据结构-跳表(基于JAVA的实现)_第1张图片

跳跃表应用场景分析

在Server端,对并发和性能有要求的情况下,如何选择合适的数据结构(跳表和红黑树)?
如果单纯比较性能,跳跃表和红黑树可以说相差不大,但是加上并发的环境就不一样了,如果要更新数据,跳表需要更新的部分就比较少,锁的东西也就比较少,所以不同线程争锁的代价就相对少了。而红黑树有个平衡的过程,牵涉到大量的节点,争锁的代价也就相对较高了。性能也就不如前者了。

在并发环境下Skip List有另外一个优势,红黑树在插入和删除的时候可能需要做一些rebalance的操作,这样的操作可能会涉及到整个树的其他部分,而Skip List的操作显然更加局部性一些,锁需要盯住的节点更少,因此在这样的情况下性能好一些。

在Redis中,有序集数据类型(Sorted Set)也是用跳表实现的。
Redis作者描述的使用跳表的原因:

1、跳表的一个缺点是耗内存(因为要重复分层存节点),但是作者也说了,可以调参数来降低内存消耗,和那些平衡树结构达到差不多。
2、redis经查有范围操作,这样利用跳表里面的双向链表,可以方便地操作。另外还有缓存区域化(cache locality)不会比平衡树差。
3、实现简单。zrank操作能够到O(log(N))。

跳跃表的JAVA应用

在Java的API中已经有了实现:

  1. ConcurrentSkipListMap. 在功能上对应HashTable、HashMap、TreeMap。
  2. ConcurrentSkipListSet . 在功能上对应HashSet.
    确切来说,Skip List更像Java中的TreeMap。TreeMap基于红黑树(一种自平衡二叉查找树)实现的,时间复杂度平均能达到O(log n),TreeMap输出是有序的,ConcurrentSkipListMap和ConcurrentSkipListSet 输出也是有序的(本博测试过)。下例的输出是从小到大,有序的。

使用样例

import java.util.*;
import java.util.concurrent.*;
/*
 * 跳表(SkipList)这种数据结构算是以前比较少听说过,它所实现的功能与红黑树,AVL树都差不太多,说白了就是一种基于排序的索引结构,
 * 它的统计效率与红黑树差不多,但是它的原理,实现难度以及编程难度要比红黑树简单。 
 * 另外它还有一个平衡的树形索引机构没有的好处,这也是引导自己了解跳表这种数据结构的原因,就是在并发环境下其表现很好. 
 * 这里可以想象,在没有了解SkipList这种数据结构之前,如果要在并发环境下构造基于排序的索引结构,那么也就红黑树是一种比较好的选择了,
 * 但是它的平衡操作要求对整个树形结构的锁定,因此在并发环境下性能和伸缩性并不好.
 * 在Java中,skiplist提供了两种:
 * ConcurrentSkipListMap 和 ConcurrentSkipListSet 
 * 两者都是按自然排序输出。
 */
public class SkipListDemo {	
	public static void skipListMapShow(){
	Map<Integer,String> map= new ConcurrentSkipListMap<>();
	map.put(1, "1");
	map.put(23, "23");
	map.put(3, "3");
	map.put(2, "2");
	for(Integer key : map.keySet()){
		System.out.println(map.get(key));
	 }
	}
	
	public static void skipListSetShow(){
		Set<Integer> mset= new ConcurrentSkipListSet<>();
		mset.add(1);
		mset.add(21);
		mset.add(6);
		mset.add(2);
		System.out.println("ConcurrentSkipListSet result="+mset);
		
		Set<String> myset = new ConcurrentSkipListSet<>();
		System.out.println(myset.add("abc"));
		System.out.println(myset.add("fgi"));
		System.out.println(myset.add("def"));
		System.out.println(myset.add("Abc"));
		System.out.println("ConcurrentSkipListSet contains="+myset);
		}
}

输出结果:

1
2
3
23

ConcurrentSkipListSet result=[1, 2, 6, 21]

true
true
true
true
ConcurrentSkipListSet contains=[Abc, abc, def, fgi]

源码分析

ConcurrentSkipListMap

ConcurrentSkipListMap和TreeMap类似,它们虽然都是有序的哈希表。但是,第一,它们的线程安全机制不同,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的。第二,ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的。

ConcurrentSkipListMap的数据结构,如下图所示:
数据结构-跳表(基于JAVA的实现)_第2张图片跳表分为许多层(level),每一层都可以看作是数据的索引,这些索引的意义就是加快跳表查找数据速度。每一层的数据都是有序的,上一层数据是下一层数据的子集,并且第一层(level 1)包含了全部的数据;层次越高,跳跃性越大,包含的数据越少。跳表包含一个表头,它查找数据时,是从上往下,从左往右进行查找。

先以数据“7,14,21,32,37,71,85”序列为例,来对跳表进行简单说明。在跳表中查找“32”节点 路径如下图所示:
数据结构-跳表(基于JAVA的实现)_第3张图片

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentNavigableMap<K,V>,
               Cloneable,
               java.io.Serializable {
    //head是跳表的表头
    private transient volatile HeadIndex<K,V> head;
}

static class Index<K,V> {
    final Node<K,V> node;       //哈希表节点node
    final Index<K,V> down;      //下索引的指针
    volatile Index<K,V> right;  //右索引的指针
}

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;
    }
}

static final class Node<K,V> {
    final K key;
    volatile Object value;
    volatile Node<K,V> next;
}

ConcurrentSkipListSet

ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它包含一个ConcurrentNavigableMap对象m,而m对象实际上是ConcurrentNavigableMap的实现类ConcurrentSkipListMap,它只用到了ConcurrentSkipListMap中的key,其value是一个空的Object。

public class ConcurrentSkipListSet<E>
    extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable {
    private final ConcurrentNavigableMap<E,Object> m;

    public ConcurrentSkipListSet() {
        m = new ConcurrentSkipListMap<E,Object>();
    }
}

你可能感兴趣的:(数据结构)