Redis中使用跳錶做爲有序集合鍵(zset)的底層實現之一。當有序集合元素較多時,或有序集合中元素爲較長字符串時,都會使用跳錶做爲底層結構。java
Redis在兩個地方用到了跳錶,一種是有序集合鍵中,另外一種是集羣節點中作內部數據結構。node
下面的結構是就是跳錶:數據結構
其中 -1 表示 INT_MIN, 鏈表的最小值,1 表示 INT_MAX,鏈表的最大值。app
跳錶具備以下性質:less
(1) 由不少層結構組成dom
(2) 每一層都是一個有序的鏈表ide
(3) 最底層(Level 1)的鏈表包含全部元素 越接近頂層的鏈表,含有的節點則越少 (通常是作爲索引層)函數
(4) 若是一個元素出如今 Level i 的鏈表中,則它在 Level i 之下的鏈表也都會出現。測試
(5) 每一個節點包含4個指針,一個指向同一鏈表中的下一個元素,一個指向下面一層的元素。一個指向上面一層的元素,一個指向同一鏈表中的上一個元素ui
package com.high_structure; import java.util.Random; public class MySkipList<K extends Comparable<K>, V> { // 該類應該的有的屬性 size head節點 private int size; private Node<K, V> head; // 由於咱們會經過隨機數機率的方式去生成是否生成索引層 private final Random random = new Random(); // private final double DEFAULT_PROBABILITY = 0.5; public MySkipList() { super(); this.size = 0; this.head = new Node<K, V>(null, null, 0); } // 提供幾個基本方法 方便後期去操做鏈表 1 判斷是否爲空 2水平插入到某個元素的後面 3 垂直插入(上下關聯) public boolean isEmpty() { return size == 0; } /** * 吧b插入到a的後面<br> * a->c->d <br> * b * * @param a * @param b */ public void horizontalLink(Node<K, V> a, Node<K, V> b) { b.setPre(a); b.setNext(a.getNext()); if (a.getNext() != null) // 創建從右到左的連接關係 { // 在這裏a.getnext == b。getnext的 a.getNext().setPre(b); } a.setNext(b); } /** * 吧b插入到a下面 * * @param a * @param b */ public void vertLink(Node<K, V> a, Node<K, V> b) { a.setDown(b); b.setUp(a); } public static void main(String[] args) { MySkipList<Integer, String> skipList = new MySkipList<>(); skipList.add(151, "1"); skipList.add(12, "12"); skipList.add(121, "121"); skipList.add(112, "112"); skipList.add(11, "11"); skipList.remove(12); skipList.remove(121); skipList.remove(15); System.out.println(skipList); } /** * 判斷key1是否小於key2 * * @param key1 * @param key2 * @return */ public boolean lessthanOrEquals(K key1, K key2) { return key1.compareTo(key2) <= 0; } public Node<K, V> get(K key) { // 省略key的判斷 Node<K, V> node = searchNode(key); if (node.getKey().equals(key)) { return node; } return null; } /** * 經過指定的key查找元素 * * @param key * @return */ public Node<K, V> searchNode(K key) { // 該node表示最後返回的節點 Node<K, V> node = head; // 該節點表示遍歷會用到的節點 Node<K, V> next = null; Node<K, V> down = null; // 先在從head開始查找 while (true) { next = node.getNext(); // 先遍歷第一層節點 while (next != null && lessthanOrEquals(next.getKey(), key)) // 若是不爲空 而且遍歷的節點<=當前節點 { node = next; next = next.getNext(); } if (node.getKey() != null && node.getKey().compareTo(key) == 0) { break; } // 若是不相等 進入下一層級 down = node.getDown(); if (down == null) // 最後一層 { break; } else { node = down; } } return node; } /** * 添加元素 若是 key存在 就修改該node的value 若是不存在 就添加到他應該的位置 * * @param node */ public void add(K key, V val) { // 省略判斷key是否合法 Node<K, V> node = searchNode(key); K searchKey = node.getKey(); if (searchKey != null && searchKey.compareTo(key) == 0) { node.setValue(val); return; } // 若是不相等 那麼咱們就把咱們的節點插入到該節點的後面 Node<K, V> newNode = new Node<K, V>(key, val, node.getLevel()); horizontalLink(node, newNode); // 經過隨機函數的的方式看是否須要生成索引層 【若是查找的數和咱們 的head在同一層在同一級就須要生成 】 int headLevel = head.getLevel(); int currentLevel = node.getLevel(); while (isNeedBuildLevel()) { if (currentLevel >= headLevel) { // 須要建立的心的索引層頭結點 Node<K, V> newHead = new Node<K, V>(null, null, ++headLevel); vertLink(newHead, head); head = newHead; } // 結合跳錶的圖更好理解 // 若是當前節點(node)的up爲空 咱們就跳轉到當前節點的head節點 而後 獲取up // 若是不爲空直接獲取up 而後在up後面添加當前節點做爲索引層 while (node.getUp() == null) { node = node.getPre(); } node = node.getUp(); Node<K, V> newTemp = new Node<K, V>(key, val, node.getLevel()); horizontalLink(node, newTemp); vertLink(newTemp, newNode);
//由於咱們生成索引層是隨機的 有可能在生成了一個索引層之後又會生成一個
//好比 如今有2層
// leav1 0--> 1
// ||
// leav0 0--> 1 假如又生成一個新的索引層 咱們實際上是須要記錄上一次索引層的位置 也就是 tmp的值 咱們把他保存在newNode 下次使用
newNode = newTemp; currentLevel++; } size++; } /** * * 刪除的核心思路 先找到該節點 而後走到最下面 從下面開始刪除 (從上往下刪除 ) * * @param key */ public void remove(K key) { // 省略判斷key是否合法 Node<K, V> node = searchNode(key); // 刪除 if (node != null && node.getKey().equals(key)) { while (node.getDown() != null) { node = node.getDown(); } Node<K, V> next = null; Node<K, V> pre = null; while (node != null) { pre = node.getPre(); next = node.getNext(); if (pre != null) { pre.setNext(next); } if (next != null) { next.setPre(pre); } node = node.getUp(); } // 對頂層鏈表進行調整,去除無效的頂層鏈表 某些時候可能存在某一行索引就一個單獨的頭結點 該頭結點的next沒有值 while (head.getNext() == null && head.getDown() != null) { head = head.getDown(); head.setUp(null); } } size--; } @Override public String toString() { StringBuilder sb = new StringBuilder(); Node<K, V> node = head; // 移動到最底層 while (node.getDown() != null) node = node.getDown(); while (node.getPre() != null) node = node.getPre(); // 第一個節點是頭部節點,沒有任何意義,因此須要移動到後一個節點 if (node.getNext() != null) node = node.getNext(); // 遍歷 while (node != null) { sb.append(node.toString()).append("\n"); node = node.getNext(); } return sb.toString(); } private boolean isNeedBuildLevel() { return random.nextDouble() < DEFAULT_PROBABILITY; } // 節點類型 採用內部類的方式 static class Node<K, V> { private K key; private V value; // 還須要一個值來保存該節點所在的層級 private int level; public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } private Node<K, V> up, down, next, pre; public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } public Node<K, V> getUp() { return up; } public void setUp(Node<K, V> up) { this.up = up; } public Node<K, V> getDown() { return down; } public void setDown(Node<K, V> down) { this.down = down; } public Node<K, V> getNext() { return next; } public void setNext(Node<K, V> next) { this.next = next; } public Node<K, V> getPre() { return pre; } public void setPre(Node<K, V> pre) { this.pre = pre; } public Node(K key, V value, int level) { super(); this.key = key; this.value = value; this.level = level; } public Node() { } @Override public String toString() { return "Node [key=" + key + ", value=" + value + "]"; } } }
最後的測試結果以下