Leveldb源碼解析第五篇【memtable之skiplist】

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。

前面講到了在 table 中插入數據,然後將數據持久化到磁盤中,這些都是一下底層的操作,用戶真正寫數據是放到內存中。本章就來介紹 key-value 在內存的操作

skiplist

key-value 存放在在內存中採用的結構是 skiplist,結構如下所示

head_------>7------------------------------------------>31
            |                                           |
head_------>7------------------------------------------>31--------------------------------->78
            |                                           |                                   |
head_------>7--------------->15------------------------>31--------------->45--------------->78
            |                |                          |                 |                 |
head_------>7------>10------>15------>24------>29------>31------>34------>45------>56------>78------>100

如上結構,分爲 4 層,每一層都是一個有序單向鏈表。這樣一個結構該怎麼操作呢,接下來用查詢和添加數據來操作 skiplist
1. 首先是查詢,如果我們要找到內容爲 29key
- 首先 skiplist 有一個變量 head_ 始終指向第一個節點的最上層,也就是圖中最上層的 head_
- 29 和第 4head_next 節點 7 比較,大於 7,此時頭節點指向第 4 層的 7
- 29 和第 47next 節點 31 比較,小於 31,降一層
- 29 和第 37next 節點 31 比較,小於 31,降一層
- 29 和第 27next 節點 15 比較,大於 15,此時頭節點指向第 2 層的 15
- 29 和第 215next 節點 31 比較,小於 31,降一層
- 29 和第 115next 節點 24 比較,大於 24,此時頭節點指向第 1 層的 24
- 29 和第 124next 節點 29 比較,等於 29,且是第一層,沒錯就是你了

  1. 在上圖中插入 30

    • 首先得到一個隨機值( 1kMaxHeight 之間)來設置 30 這個節點的層數,假設得到的是 5,定義一個 prev[5],用來存放 30 的前一個節點
    • 上圖中也就 4 層,咋整?(skiplist 有兩個關於層數的變量,第一個是 kMaxHeight,表示允許的最大層;第二個是 max_height_,表示 skiplist 當前最大層,head_ 節點在初始話的時候和其他節點不一樣,層數不是隨機得到的,而是直接指定的 kMaxHeight
    • 沒事,將大於 4 的層以上的節點的 prev 全部指向 head_,也就是將 prev[4] = head_(第5層), 並將當前最大層數 max_height_ 設置爲 5
    • 還是按照上面查詢的步驟,只不過把每次降一層時的節點放在 prev 中,那麼

      prev[4] = head_(第5層);
      prev[3] = 7(第4層);
      prev[2] = 7(第3層);
      prev[1] = 15(第2層);
      prev[0] = 29(第1層);
    • 得到 prev 後,初始化一個高度爲 5 值爲 30 的節點 x

      for i in 0..4:
      x[i]->next = prev[i]->next;
      prev[i]->next = x[i];
    • 得到的結果就是
        head_-------------------------------------------------->30
                                                                |
        head_------>7------------------------------------------>30------>31
                    |                                           |        |
        head_------>7------------------------------------------>30------>31--------------------------------->78
                    |                                           |        |                                   |
        head_------>7--------------->15------------------------>30------>31--------------->45--------------->78
                    |                |                          |        |                 |                 |
        head_------>7------>10------>15------>24------>29------>30------>31------>34------>45------>56------>78------>100

Node

skiplist 應該解釋的差不多了,接下來介紹一下 skiplist 的節點 struct Node

// 模板方法,其實很好理解,相當於佔位,Key和Comparator可以替換成任何類型
template<typename Key, class Comparator>
struct SkipList<Key,Comparator>::Node {
  // 不允許隱式構造
  explicit Node(const Key& k) : key(k) { }
  // 結構體中有個Key類型的key
  Key const key;

  // 返回next_[n],也就是當前節點第n層的next節點,不要被return的那一長串嚇到,後面會介紹到
  Node* Next(int n) {
    assert(n >= 0);
    return reinterpret_cast<Node*>(next_[n].Acquire_Load());
  }

  // 設置當前節點第n層的next節點爲x,不要被return的那一長串嚇到,後面會介紹到
  void SetNext(int n, Node* x) {
    assert(n >= 0);

    next_[n].Release_Store(x);
  }

  // 和上面差不多,區別是沒有進行內存隔離,後面會介紹到
  Node* NoBarrier_Next(int n) {
    assert(n >= 0);
    return reinterpret_cast<Node*>(next_[n].NoBarrier_Load());
  }
  // 和上面差不多,區別是沒有進行內存隔離,後面會介紹到
  void NoBarrier_SetNext(int n, Node* x) {
    assert(n >= 0);
    next_[n].NoBarrier_Store(x);
  }

 private:
  // 這裏定義長度爲1的port::AtomicPointer對象,其實是一個變長結構體,next_指向的是第一個
  port::AtomicPointer next_[1];
};

Node 中有兩個變量,keynext_ 數組,key 是用來存放值的,next_ 是用來下一個節點指向的

AtomicPointer

關於內存隔離請看 leveldb-AtomicPointer

skipList 函數實現

  void Insert(const Key& key);
  bool Contains(const Key& key) const;
  Node* FindGreaterOrEqual(const Key& key, Node** prev) const;

skipList 主要包含這兩個函數

// 得到一個隨機的高度
template<typename Key, class Comparator>
int SkipList<Key,Comparator>::RandomHeight() {
  static const unsigned int kBranching = 4;
  // 初始爲 1
  int height = 1;
  // 得到一個隨機值,如果隨機值是 4 的倍數就返回 height,否則 keight 就加 1,爲什麼要這麼做?
  // 如果我們得到一個隨機值,直接對 kMaxHeight 取模加 1,然後賦值給 height,那麼 height 在 [1~12] 之前出現的概率一樣的
  // 如果節點個數爲 n,那麼有 12 層的節點有 n/12 個,11 層的有 n/12+n/12(需要把12層的也加上),節點太多,最上層平均前進一次才右移 12 個節點,下面層就更不用說了,效率低;
  // 作者的方法是每一層會按照4的倍數減少,出現4層的概率只有出現3層概率的1/4,這樣查詢起來效率是不是大大提升了呢
  while (height < kMaxHeight && ((rnd_.Next() % kBranching) == 0)) {
    height++;
  }
  assert(height > 0);
  assert(height <= kMaxHeight);
  return height;
}

// 很簡單,就是比較key和n的大小
template<typename Key, class Comparator>
bool SkipList<Key,Comparator>::KeyIsAfterNode(const Key& key, Node* n) const {
  // NULL n is considered infinite
  return (n != NULL) && (compare_(n->key, key) < 0);
}

template<typename Key, class Comparator>
typename SkipList<Key,Comparator>::Node* SkipList<Key,Comparator>::FindGreaterOrEqual(const Key& key, Node** prev)
    const {
  // x指向最上層頭指針
  Node* x = head_;
  // 得到當前最高層
  int level = GetMaxHeight() - 1;
  while (true) {
    // next指向的是x同層的下一個節點,可能爲空
    Node* next = x->Next(level);
    // 如果key在next的後面,x指針指向next
    if (KeyIsAfterNode(key, next)) {
      // Keep searching in this list
      x = next;
    } else {
      // 如果next爲空或key在next的前面
      if (prev != NULL) prev[level] = x;
      if (level == 0) {
        // 層數爲0的話,說明兩個節點之間沒有挑過其他節點,而此時next是大於或等於key的,key肯定是大於next的前一個節點的(不大於的話走不到next節點)
        // 返回的next節點只能大於或等於key節點
        return next;
      } else {
        // 如果不是最底層的話就降一層
        level--;
      }
    }
  }
}

template<typename Key, class Comparator>
bool SkipList<Key,Comparator>::Contains(const Key& key) const {
  // 找到大於或等於key的節點,如果相等就返回true
  Node* x = FindGreaterOrEqual(key, NULL);
  if (x != NULL && Equal(key, x->key)) {
    return true;
  } else {
    return false;
  }
}

skipList 內部類 Iterator

// Iterator方法實現比較簡單,這裏就詳細介紹了
class Iterator {
   public:
    explicit Iterator(const SkipList* list);
    // 判斷當前節點是否有效
    bool Valid() const;
    // 返回當前節點的key
    const Key& key() const;
    // 返回第0層的下一個節點,需要判斷是否有效
    void Next();
    // 返回第0層的上一個節點,需要判斷是否有效
    void Prev();
    // 找到第0層第一個大於或等於target的節點
    void Seek(const Key& target);
    // 返回第0層的第一個節點
    void SeekToFirst();
    // 返回第0層最後一個節點
    void SeekToLast();

   private:
    const SkipList* list_;
    Node* node_; // 當前節點
  };

總結

學習skiplist主要了解三個地方,第一個是Node的定義(內存隔離);第二個是skiplist類本身的幾個方法;第三個就是skiplist的內部類了,用來遍歷鏈表

【作者:antonyxu https://antonyxux.github.io/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章