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

来源:互联网 发布:儿童画图软件 编辑:程序博客网 时间:2024/05/18 02:52

版权声明:本文为博主原创文章,未经博主允许不得转载。

前面讲到了在 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/ 】

原创粉丝点击