데이터 구조 점프 테이블 (JAVA 구현 기반)
스킵 미터의 원리와 특징
스킵 테이블의 본질은 바이너리 검색을 수행 할 수있는 정렬 된 링크리스트인데, 스킵 테이블은 원래의 정렬 된 링크리스트에 다단계 인덱스를 추가하여 인덱스를 통해 빠른 검색을 실현합니다. 연결된리스트의 느린 쿼리 문제를 해결하지만 더 많은 메모리를 차지하게되며, 메모리 공간을 교환하는 데이터 구조입니다.
임의 데이터 쿼리 시간 복잡도 O (logn)
데이터 삽입 시간 복잡도 O (logn)
공간 복잡도 : O (n)
점프 테이블 적용 시나리오 분석
서버 측에서 동시성과 성능에 대한 요구 사항이있을 때 적절한 데이터 구조 (점프 테이블 및 레드-블랙 트리)를 선택하는 방법은 무엇입니까?
단순히 성능을 비교하면 점프 테이블과 레드-블랙 트리는 동일하다고 할 수 있지만 동시성 환경은 다릅니다. 데이터를 업데이트하려는 경우 점프 테이블의 업데이트가 적고 잠금이 더 적습니다. , 따라서 잠금을 위해 경쟁하는 서로 다른 스레드의 비용은 상대적으로 적습니다. 레드-블랙 트리는 많은 수의 노드를 포함하는 균형 잡힌 프로세스를 가지고 있으며 잠금에 대한 경합 비용이 상대적으로 높습니다. 성능은 이전만큼 좋지 않습니다.
동시 환경에서 Skip List는 또 다른 장점이 있습니다. Red-black 트리는 삽입 및 삭제시 일부 재조정 작업을 수행해야 할 수 있습니다. 이러한 작업은 전체 트리의 다른 부분을 포함 할 수 있으며 Skip List 작업은 분명히 더 로컬입니다. 일부의 경우 잠금이 집중해야하는 노드가 적으므로이 경우 성능이 더 좋습니다.
Redis에서는 정렬 된 세트 데이터 유형 (Sorted Set)도 점프 테이블로 구현됩니다.
Redis 작성자는 점프 테이블을 사용하는 이유를 설명했습니다.
1. 점프 테이블의 한 가지 단점은 메모리 소비 (노드의 계층 적 반복 저장으로 인한)이지만, 저자는 또한 균형 잡힌 트리 구조와 유사한 메모리 소비를 줄이기 위해 매개 변수를 조정할 수 있다고 말했습니다.
2. Redis는 조사 후 다양한 작업을 수행하므로 점프 테이블에서 이중 연결 목록을 사용하여 쉽게 작업 할 수 있습니다. 또한 캐시 지역 성은 균형 잡힌 트리보다 나쁘지 않습니다.
3. 구현이 간단합니다. zrank 연산은 O (log (N))에 도달 할 수 있습니다.
점프 테이블의 JAVA 응용
Java API에서 구현되었습니다.
- ConcurrentSkipListMap. 함수에서 HashTable, HashMap, TreeMap에 해당합니다.
- ConcurrentSkipListSet. 기능적으로 HashSet에 해당합니다.
정확히 말하면 Skip List는 Java의 TreeMap과 비슷합니다. TreeMap은 red-black 트리 (자체 균형 이진 검색 트리)를 기반으로 구현됩니다. 평균 시간 복잡도는 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 결과 = [1, 2, 6, 21]
true
true
true
true
ConcurrentSkipListSet contains = [Abc, abc, def, fgi]
소스 코드 분석
ConcurrentSkipListMap
ConcurrentSkipListMap은 둘 다 순서가 지정된 해시 테이블이지만 TreeMap과 유사합니다. 그러나 먼저 스레드 안전 메커니즘이 다릅니다 .TreeMap은 스레드로부터 안전하지 않은 반면 ConcurrentSkipListMap은 스레드로부터 안전합니다. 둘째, ConcurrentSkipListMap은 테이블을 점프하여 구현하고 TreeMap은 red-black 트리로 구현합니다.
ConcurrentSkipListMap의 데이터 구조는 다음 그림과 같습니다.
스킵 테이블은 여러 레벨로 나누어 져 있으며 각 레이어는 데이터의 인덱스로 볼 수 있습니다. 이러한 인덱스의 의미는 스킵 테이블에서 데이터 검색 속도를 높이기위한 것입니다. 각 계층의 데이터가 정렬되고 상위 계층의 데이터는 다음 계층의 데이터의 하위 집합이며 첫 번째 계층 (수준 1)에는 모든 데이터가 포함됩니다. 수준이 높을수록 점프가 커지고 포함 된 데이터가 포함됩니다. 적게. 점프 테이블에는 헤더가 포함되어 있으며 데이터를 검색 할 때 위에서 아래로, 왼쪽에서 오른쪽으로 검색합니다.
먼저 데이터 "7,14,21,32,37,71,85"시퀀스를 예로 들어 점프 테이블을 간략하게 설명합니다. 아래 그림과 같이 점프 테이블에서 "32"노드 경로를 찾습니다.
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 ConcurrentSkipListMap의 구현 클래스 인 ConcurrentNavigableMap 객체 m을 포함합니다. ConcurrentSkipListMap의 키만 사용하고 해당 값은 빈 객체입니다.
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>();
}
}