所謂跳躍表,就是在普通鏈表的基礎上增長了多層索引鏈表,這樣在查找時就能夠經過在上下不一樣層級的索引鏈表間跳躍,以達到快速查找的目的。java
固然,我這樣說可能比較抽象,下面我用一張圖來簡單解釋一下。node
從上面這張圖能夠看出,跳躍表有三種不一樣的對象。git
圖中最下層是真正存儲數據的數據鏈表,其中每一個Node節點
都有三個字段:數組
此外,數據鏈表還有一個特色,那就是它是順序存儲的,經過插入數據時處理實現。bash
經過觀察圖中的索引鏈表,咱們能夠發現它有一個特色,那就是它是分層級的,並且同時指向它的下層索引節點以及同層級的右側的索引節點。所以,每一個Index節點
也有三個字段:數據結構
Index節點
都指向最下層的同一個Node節點
,目的是能夠隨時經過索引節點判斷出它對應的數據節點是哪一個仔細觀察上面的示意圖能夠發現,頭部索引鏈表本質上也是索引鏈表,只是在普通的索引鏈表的基礎上新增了一個表示當前橫向鏈表層級的字段。所以,每一個HeadIndex節點
就須要用四個字段來表示:dom
Node節點
,這個Node節點
只是起到一個標誌性做用,所以沒有KEY仍是用上面那個示意圖舉例,咱們來看兩個典型的查找示例。ide
很明顯,先從左上角的HeadIndex節點
開始查找。測試
Index[G]
,其數據節點大於D,因此不能移動到右側的Index[G]
;HeadIndex節點
繼續向右查找,Index[B]
的數據節點的KEY是B,小於D,所以移動到第二層的Index[B]
;Index[B]
跟右側的Index[G]
比較,發現不能繼續向右移動;Index[B]
,而後跟右側的Index[F]
比較,發現不能繼續向右移動;看完上面的例子後,相信你應該已經明白,在跳躍表中查找數據通常須要如下幾個步驟:大數據
HeadIndex節點
開始查找;根據上面敘述的通常性步驟,下面咱們經過一張示意圖再來看看查找H須要什麼步驟。
最後,我上面畫的這個示意圖已經表示得很清楚了,所以文字性敘述我就再也不多說了。
在上面的示意圖能夠看出,跳躍表主要分爲兩個部分:數據鏈表和索引鏈表。所以,在插入數據的時候首先須要作的是將數據插入到數據鏈表中,而後再給新插入的數據設置一列合理的索引。
咱們這裏仍是以上面那個圖爲例,假設節點B是新插入到跳躍表的節點,咱們來看一下整個插入過程須要作哪些步驟。
由於跳躍表是在普通鏈表的基礎上增長了多個層級的索引鏈表,以實現快速查找的目的,因此它的核心功能仍然有一個數據鏈表來存儲數據,所以咱們須要作的第一步就是將數據插入到數據鏈表合適的位置。
有了上面查詢流程的介紹,這個步驟的實現就很簡單了,總共須要如下兩步來實現:
在將數據插入到數據鏈表後,咱們還須要作的是爲這個新的數據節點創建合適的索引。這一個步驟沒有標準答案,只須要爲新節點創建隨機層級的索引節點就能夠了,固然最好還能夠作到均勻分佈。
在Java8
的ConcurrentSkipListMap
源碼中,它設置的策略是:
在上面那個步驟完成索引節點的初始化後,這一步須要作的是將各層級的索引節點關聯到原來的橫向索引鏈表中。所以能夠經過如下四步來實現:
HeadIndex節點
開始查找;跳躍表的刪除實際上就是插入過程的逆向操做,所以理解了上面是怎麼插入的,天然就知道如何刪除了。固然,主要步驟也是三個:
在上面,我花了很大篇幅介紹跳躍表的基本操做步驟,下面我將給出一份用Java實現的跳躍表,詳細邏輯能夠參考上述說明以及代碼中的註釋。
完整源碼地址:gitee.com/zifangsky/D…
package cn.zifangsky.skiplist;
import cn.zifangsky.hashtable.Map;
import java.text.MessageFormat;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiConsumer;
/** * 跳錶 * * @author zifangsky * @date 2019/8/8 * @since 1.0.0 */
public class SkipListMap <K extends Comparable, V> implements Map<K, V> {
/** * 單個節點信息 */
static class Node<K extends Comparable, V> {
/** * key */
final K key;
/** * value */
volatile Object value;
/** * 下一個節點 */
volatile Node<K, V> next;
public Node(K key, V value) {
this.key = key;
this.value = value;
this.next = null;
}
public Node(K key, Object value, Node<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
/** * 返回是不是「鏈表最左下角的 BASE_HEADER 節點」 */
boolean isBaseHeader(){
return value == BASE_HEADER;
}
/** * 返回是不是數據節點 */
V getValidValue() {
Object v = value;
if (v == this || this.isBaseHeader()) {
return null;
}
return (V)v;
}
/** * 跟另外一個節點比較KEY的大小 * @param o 另外一個節點 * @return int */
int compareKeyTo(Node<K, V> o){
return this.key.compareTo(o.key);
}
/** * 當前節點KEY跟另外一個節點的KEY比較 * @param key 另外一個節點的KEY * @return int */
int compareKeyTo(K key){
return this.key.compareTo(key);
}
@Override
public final String toString() {
return key + "=" + value;
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Node<?, ?> node = (Node<?, ?>) o;
return SkipListMap.equals(key, node.key) &&
SkipListMap.equals(value, node.value);
}
/** * 取 key 和 value 的 hashCode 的異或 */
@Override
public final int hashCode() {
return SkipListMap.hashCode(key) ^ SkipListMap.hashCode(value);
}
}
/** * 索引節點 */
static class Index<K extends Comparable, V> {
/** * 索引指向的最底層的數據節點 */
final Node<K, V> node;
/** * 下邊的索引節點 */
final Index<K, V> down;
/** * 右邊的索引節點 */
volatile Index<K, V> right;
public Index(Node<K, V> node, Index<K, V> down) {
this(node, down, null);
}
public Index(Node<K, V> node, Index<K, V> down, Index<K, V> right) {
this.node = node;
this.down = down;
this.right = right;
}
}
/** * 頭部索引節點 */
static class HeadIndex<K extends Comparable, V> extends Index<K, V>{
/** * 用於標識當前索引的層級 */
int level;
public HeadIndex(Node<K, V> node, Index<K, V> down, Index<K, V> right, int level) {
super(node, down, right);
this.level = level;
}
}
/* ---------------- Fields -------------- */
/** * 初始首節點 */
private static final Object BASE_HEADER = new Object();
/** * 默認第一層的level */
public static final int DEFAULT_LEVEL = 1;
/** * 整個跳躍表的最頂層的頭節點 */
private transient volatile HeadIndex<K, V> head;
/** * 索引的level */
private volatile int level;
public SkipListMap() {
this.initialize();
}
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
@Override
public int size() {
int count = 0;
for(Node<K, V> temp = this.findFirst(); temp != null; temp = temp.next){
if(temp.getValidValue() != null){
count++;
}
}
return count;
}
@Override
public boolean isEmpty() {
return this.findFirst() == null;
}
@Override
public boolean containsKey(K key) {
return this.getNode(key) != null;
}
@Override
public V get(K key) {
Node<K, V> temp;
return (temp = this.getNode(key)) != null ? (V) temp.value : null;
}
@Override
public void put(K key, V value) {
this.putVal(key, value);
}
@Override
public void remove(K key) {
this.removeVal(key);
}
@Override
public void clear() {
this.initialize();
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
}
/** * 格式化遍歷輸出 */
public void forEach() {
HeadIndex<K, V> headIdx = this.head;
//1. 獲取索引節點的數組
Index<?, ?>[] idxArr = new Index<?, ?>[this.level];
while (headIdx != null){
idxArr[headIdx.level-1] = headIdx.right;
headIdx = (HeadIndex<K, V>) headIdx.down;
}
//2. 獲取第一個數據節點
Node<K, V> node = this.findFirst();
//3. 遍歷索引節點
for(int i = this.level; i > 0; i--){
System.out.print(MessageFormat.format("HeadIdx[level={0}]", i));
if(idxArr.length == this.level){
Index<?, ?> idx = idxArr[i - 1];
Node<K, V> tmpNode = node;
//以數據鏈表爲基準遍歷索引列表,若是某個數據節點不存在索引則用佔位符替代
while (tmpNode != null){
if(idx != null && tmpNode.equals(idx.node)){
System.out.print(MessageFormat.format(", Idx[key={0}]", tmpNode.key));
idx = idx.right;
}else{
System.out.print(", Idx[null]");
}
tmpNode = tmpNode.next;
}
}
System.out.print("\n");
}
//4. 遍歷數據節點
System.out.print("HeadNode[BASE_HEADER]");
while (node != null){
System.out.print(MessageFormat.format(", Node[key={0}, value={1}]", node.key, node.value));
node = node.next;
}
System.out.print("\n");
}
/** * 初始化 */
private void initialize() {
level = DEFAULT_LEVEL;
head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null),
null, null, DEFAULT_LEVEL);
}
/** * 獲取第一個數據節點 */
private Node<K, V> findFirst(){
Node<K, V> preNode = this.head.node;
//數據部分的節點
Node<K, V> node = preNode.next;
if(node != null){
return node;
}else{
return null;
}
}
/** * 移除某個鍵值對 */
private void removeVal(K key){
//1. 刪除數據節點
//1.1 獲取key的前置數據節點
Node<K, V> preNode = this.findPreNode(key);
//下一個數據節點
Node<K, V> node = preNode.next;
//1.2 判斷是不是咱們查找的那個節點
if(node == null || node.key.compareTo(key) != 0){
return;
}
//1.3 將數據節點從鏈表上移除
preNode.next = node.next;
//2. 刪除索引節點
Index<K, V> preLevelIdx = this.findDownPreIdx(this.head, node);
if(preLevelIdx != null){
//2.1 獲取目標節點的索引節點
Index<K, V> levelIdx = preLevelIdx.right;
//2.2 從新關聯索引鏈表
preLevelIdx.right = levelIdx.right;
//2.3 繼續刪除下層的索引節點
while (preLevelIdx != null){
preLevelIdx = this.findDownPreIdx(preLevelIdx, node);
if (preLevelIdx != null) {
levelIdx = preLevelIdx.right;
preLevelIdx.right = levelIdx.right;
}
}
}
}
/** * 根據 key 查找對應數據節點 */
private Node<K, V> getNode(K key){
//1. 獲取key的前置數據節點
Node<K, V> preNode = this.findPreNode(key);
//下一個數據節點
Node<K, V> node = preNode.next;
//2. 判斷是不是咱們查找的那個節點
if(node != null && node.key.compareTo(key) == 0){
return node;
}else{
return null;
}
}
/** * 存儲某個鍵值對 */
private void putVal(K key, V value){
//1. 獲取key的前置數據節點
Node<K, V> preNode = this.findPreNode(key);
//獲取後置節點,做爲新節點的後置節點
Node<K, V> nextNode = preNode.next;
//若是發現重複節點,則直接替換值並返回
if(nextNode != null && nextNode.compareKeyTo(key) == 0){
nextNode.value = value;
preNode.next = nextNode;
return;
}
//2. 建立新的數據節點
Node<K, V> newNode = new Node<>(key, value);
//3. 將新節點掛載到鏈表
newNode.next = nextNode;
preNode.next = newNode;
//4. 設置新節點的索引
this.createNodeIndex(newNode);
}
/** * 設置新插入數據節點的索引狀況 * @param newNode 新插入到鏈表的數據節點 */
private void createNodeIndex(Node<K, V> newNode){
//1. 生成一個隨機整數
int rnd = ThreadLocalRandom.current().nextInt();
//2. 若是最高位和最低位都爲1,則直接插入鏈表(1/4的機率),其餘的須要建立索引節點
if((rnd & 0x80000001) == 0){
HeadIndex<K, V> headIdx = this.head;
Index<K, V> idx = null;
//索引節點的層級
int level = 1, maxLevel = headIdx.level;
//2.1 判斷應該創建幾層的索引節點(從右邊第2爲開始計算連續爲1的個數)
while (((rnd = rnd >>> 1) & 1) == 1){
level++;
}
//2.2 建立對應的索引節點
//若是計算出來的層級沒有超過原來最大的層級,則直接循環創建索引節點,不然須要在頂層再新建一層
if(level <= maxLevel){
for(int i = 1; i <= level; i++){
idx = new Index<>(newNode, idx);
}
}else{
//在頂層再新建一層索引節點
level = maxLevel + 1;
this.level = level;
//2.2.1 建立索引節點
for(int i = 1; i <= level; i++){
idx = new Index<>(newNode, idx);
}
//2.2.2 從新設置各層級的頭索引結點
HeadIndex<K, V> newHeadIdx = new HeadIndex<>(headIdx.node, headIdx, null, level);
HeadIndex<K, V> levelHeadIdx = newHeadIdx;
for(int i = level; i >= 1; i--){
levelHeadIdx.level = i;
levelHeadIdx = (HeadIndex<K, V>) levelHeadIdx.down;
}
//建立新的最頂層的頭節點
this.head = newHeadIdx;
headIdx = newHeadIdx;
}
//2.3 將新建立的那一列索引節點跟原來的關聯起來
Index<K, V> levelIdx = idx;
Index<K, V> preLevelIdx = this.findDownPreIdx(headIdx, headIdx.level, level, idx.node);
//橫向關聯索引節點
levelIdx.right = preLevelIdx.right;
preLevelIdx.right = levelIdx;
for(int i = level; i > 1; i--){
levelIdx = levelIdx.down;
preLevelIdx = this.findDownPreIdx(preLevelIdx, i, (i - 1), levelIdx.node);
//橫向關聯索引節點
levelIdx.right = preLevelIdx.right;
preLevelIdx.right = levelIdx;
}
}
}
/** * 查找底層的指定索引節點的前置索引節點,沒有則返回空 * @param current 當前索引位置 * @param expectedNode 目標節點 * @return cn.zifangsky.skiplist.SkipListMap.Index<K, V> */
private Index<K, V> findDownPreIdx(Index<K, V> current, Node<K, V> expectedNode){
Index<K, V> currentIdx = current;
//右邊索引節點
Index<K, V> rightIdx;
//右邊數據節點
Node<K, V> rightNode;
//不斷向右和向下搜索指定層級的前置索引節點
while (currentIdx != null){
//1. 將索引向右移動
while (true){
rightIdx = currentIdx.right;
if(rightIdx == null){
break;
}
rightNode = rightIdx.node;
//若是右邊索引節點還在目標節點的左邊,則將索引向右移動
if(rightNode.compareKeyTo(expectedNode) < 0){
currentIdx = currentIdx.right;
}else if(rightNode.compareKeyTo(expectedNode) == 0){
return currentIdx;
}else{
break;
}
}
//2. 將索引向下移動一步
currentIdx = currentIdx.down;
}
return null;
}
/** * 查找底層的指定層級的前置索引節點 * @param current 當前索引位置 * @param currentLevel 當前層級 * @param expectedLevel 待查找的層級 * @param expectedNode 目標節點 * @return cn.zifangsky.skiplist.SkipListMap.Index<K, V> */
private Index<K, V> findDownPreIdx(Index<K, V> current, int currentLevel, int expectedLevel, Node<K, V> expectedNode){
Index<K, V> currentIdx = current;
//右邊索引節點
Index<K, V> rightIdx;
//右邊數據節點
Node<K, V> rightNode;
//不斷向右和向下搜索指定層級的前置索引節點
while (currentLevel >= 1){
//1. 將索引向右移動
while (true){
rightIdx = currentIdx.right;
if(rightIdx == null){
break;
}
rightNode = rightIdx.node;
//若是右邊索引節點還在目標節點的左邊,則將索引向右移動
if(rightNode.compareKeyTo(expectedNode) < 0){
currentIdx = currentIdx.right;
}else{
break;
}
}
//2. 將索引向下移動一步
if(currentLevel > expectedLevel){
currentLevel = (currentLevel -1);
currentIdx = currentIdx.down;
}else{
break;
}
}
return currentIdx;
}
/** * 查找指定KEY的前置 * @param key KEY * @return cn.zifangsky.skiplist.SkipListMap.Node<K,V> */
private Node<K, V> findPreNode(K key){
Index<K, V> currentIdx = head;
//下邊索引節點
Index<K, V> downIdx;
//右邊索引節點
Index<K, V> rightIdx;
//右邊數據節點
Node<K, V> rightNode;
//不斷向右和向下搜索指定KEY的前置數據節點
while (true){
//1. 將索引向右移動
while (true){
rightIdx = currentIdx.right;
if(rightIdx == null){
break;
}
rightNode = rightIdx.node;
//若是右邊索引節點還在目標節點的左邊,則將索引向右移動
if(rightNode.compareKeyTo(key) < 0){
currentIdx = currentIdx.right;
}else{
break;
}
}
downIdx = currentIdx.down;
//2. 若是下邊索引節點不爲空,則將索引向下移動一步
if(downIdx != null){
currentIdx = downIdx;
}else{
break;
}
}
//3. 在數據鏈表上繼續向右移動
Node<K, V> idxNode = currentIdx.node;
while (true){
rightNode = idxNode.next;
//若是右邊數據節點還在目標節點的左邊,則將索引向右移動
if(rightNode == null || rightNode.compareKeyTo(key) >= 0){
break;
}else{
idxNode = idxNode.next;
}
}
return idxNode;
}
}
複製代碼
測試用例:
@Test
public void test(){
SkipListMap<String,Integer > skipListMap = new SkipListMap<>();
//1.1 插入
skipListMap.put("A", 1);
skipListMap.put("B", 2);
skipListMap.put("J", 10);
skipListMap.put("D", 4);
skipListMap.put("C", 3);
skipListMap.put("E", 5);
skipListMap.put("I", 9);
skipListMap.put("F", 6);
skipListMap.put("H", 8);
skipListMap.put("G", 7);
//1.2 遍歷
skipListMap.forEach();
System.out.println("--------------------------------------------------");
//2.1 查找
System.out.println("KEY=D,VALUE=" + skipListMap.get("D"));
System.out.println("KEY=H,VALUE=" + skipListMap.get("H"));
//2.2 遍歷
skipListMap.forEach();
System.out.println("--------------------------------------------------");
//3. 刪除
skipListMap.remove("B");
skipListMap.remove("C");
skipListMap.remove("G");
//3.2 遍歷
skipListMap.forEach();
System.out.println("--------------------------------------------------");
}
複製代碼
測試用例輸出以下:
HeadIdx[level=3], Idx[null], Idx[null], Idx[null], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=2], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=1], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadNode[BASE_HEADER], Node[key=A, value=1], Node[key=B, value=2], Node[key=C, value=3], Node[key=D, value=4], Node[key=E, value=5], Node[key=F, value=6], Node[key=G, value=7], Node[key=H, value=8], Node[key=I, value=9], Node[key=J, value=10]
--------------------------------------------------
KEY=D,VALUE=4
KEY=H,VALUE=8
HeadIdx[level=3], Idx[null], Idx[null], Idx[null], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=2], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadIdx[level=1], Idx[key=A], Idx[null], Idx[key=C], Idx[null], Idx[key=E], Idx[null], Idx[key=G], Idx[null], Idx[null], Idx[null]
HeadNode[BASE_HEADER], Node[key=A, value=1], Node[key=B, value=2], Node[key=C, value=3], Node[key=D, value=4], Node[key=E, value=5], Node[key=F, value=6], Node[key=G, value=7], Node[key=H, value=8], Node[key=I, value=9], Node[key=J, value=10]
--------------------------------------------------
HeadIdx[level=3], Idx[null], Idx[null], Idx[key=E], Idx[null], Idx[null], Idx[null], Idx[null]
HeadIdx[level=2], Idx[key=A], Idx[null], Idx[key=E], Idx[null], Idx[null], Idx[null], Idx[null]
HeadIdx[level=1], Idx[key=A], Idx[null], Idx[key=E], Idx[null], Idx[null], Idx[null], Idx[null]
HeadNode[BASE_HEADER], Node[key=A, value=1], Node[key=D, value=4], Node[key=E, value=5], Node[key=F, value=6], Node[key=H, value=8], Node[key=I, value=9], Node[key=J, value=10]
--------------------------------------------------
複製代碼
注:跳躍表的輸出結果是不肯定的,我這裏只是展現了其中一種輸出狀況,僅供參考。
最後,本篇文章到此結束,若是對跳躍表還有什麼不理解的地方,歡迎在下方留言,或者給我發郵件私下溝通。謝謝你們的觀看!
PS:一直不想寫數據結構相關的博客,主要是畫圖太麻煩了,2333。