Skiplist 的原理与使用

跳表(Skiplist)是一种通过增加层级来优化链表查询效率的数据结构,常用于Redis、LevelDB等数据库的MemTable。本文详细解析跳表的工作原理,包括插入、删除和查询操作,以及其在LevelDB中的具体实现。

Skiplist在很多场景下都有应用, 比如redis的有序集合, LevelDB、hbase、Cassandra、Bigtable等的MemTable。它是一个实现比较简单的带跳跃功能的搜索链表, 采用空间换时间的方式来加快链表的查询。

Skiplist原理

Skiplist本质上是一种链表, 但是我们知道链表的查询是比较慢的,时间复杂度为O(n), 为了提升查询的效率, 需要额外的存储一些信息, 使得它可以避免从头到尾一个一个遍历, 常用的做法是有序二叉树的二分查找, 可以做到时间复杂度为O(log(n)), 但是平衡二叉树的实现通常比较复杂,尤其是有添加删除的时候, 二叉树的平衡性可能被破坏, 需要旋转。
Skiplist的效率虽然没有平衡二叉树高, 但是它实现起来简单, 并且插入删除操作不需要更多复杂的操作。

Skiplist 的特点

  • Skiplist本身是一个有序链表;
  • Skiplist包含多个层级;
  • 每一个Node包含了不同层级的不同跨度的链表;
  • 越往上层, 链表的跨度越大, 该层链表的节点越少;
  • 每个Node拥有链表的层数是随机的, 在 0 ~ MaxHeight之间, 在该Node插入时确定;
  • 若某个key在第i层出现, 则0 ~ i-1 层都包含该元素;
  • 有一个Top指针, 指向最高层的第一个节点;

Skiplist插入节点:

  1. 生成随机的hight;
  2. 生成新的NewNode;
  3. 在层0 ~ height-1, 将NewNode插入到正确的位置, 更新next指针;
  4. 在层height ~ max-1,next指针指向NewNode;

插入节点
如上图的实例, 我们有一个Skiplist包含3层, 第一层有3、7、10、15、19、30共6个节点, 第二层有3、15、30共3个节点, 第三层3、30共两个节点。
如果我们要插入key 20, 并且随机的height是2, 结果如上图, 新插入的节点会出现在第一、第二层, 在这两层中又想一个一般的链表一样插入到正确的位置。

skiplist节点删除:
删除与插入比较类似, 不过要把各层的节点都删除掉, 如果该节点是最高层的唯一的节点, 需要将链表的实际高度减一。

Skiplist的查询:
Skiplist查询的过程, 是从上层向下层逐步推进, 最开始由Top指针指向最高层的第一个节点, 向后找到key小于目标节点的最大的节点, 如果不相等, 向下一层去找, 知道找到结束, 或者在第0层也没有找到。

查询节点

如上图实例, 查询的过程,黄色的箭头表明在同一个level进行的遍历, 绿色的箭头代表向下一层转变。

LevelDB 中Skiplist的使用

在LevelDB中, Skiplist用来保存MemTable中的数据, 其实现在文件https://github.com/google/leveldb/blob/master/db/skiplist.h, 其实现详情可以从该源码得到, 我们简单描述一下:

template<typename Key, class Comparator>
class SkipList {
 private:
  struct Node;

 public:
  // Create a new SkipList object that will use "cmp" for comparing keys,
  // and will allocate memory using "*arena".  Objects allocated in the arena
  // must remain allocated for the lifetime of the skiplist object.
  explicit SkipList(Comparator cmp, Arena* arena);

  // Insert key into the list.
  // REQUIRES: nothing that compares equal to key is currently in the list.
  void Insert(const Key& key);

  // Returns true iff an entry that compares equal to key is in the list.
bool Contains(const Key& key) const;
...
 private:
  enum { kMaxHeight = 12 };

  // Immutable after construction
  Comparator const compare_;
  Arena* const arena_;    // Arena used for allocations of nodes

  Node* const head_;

  // Modified only by Insert().  Read racily by readers, but stale
  // values are ok.
  port::AtomicPointer max_height_;   // Height of the entire list
}

这里, kMaxHeight定义了Skiplist的最大层数, max_height_是当前的最大层, head_代表了Top指针, compare_代表比较函数。
其中的Node, 代表每一个节点, 其定义如下:

// Implementation details follow
template<typename Key, class Comparator>
struct SkipList<Key,Comparator>::Node {
  explicit Node(const Key& k) : key(k) { }

  Key const key;

  // Accessors/mutators for links.  Wrapped in methods so we can
  // add the appropriate barriers as necessary.
  Node* Next(int n) {
    assert(n >= 0);
    // Use an 'acquire load' so that we observe a fully initialized
    // version of the returned Node.
    return reinterpret_cast<Node*>(next_[n].Acquire_Load());
  }
  void SetNext(int n, Node* x) {
    assert(n >= 0);
    // Use a 'release store' so that anybody who reads through this
    // pointer observes a fully initialized version of the inserted node.
    next_[n].Release_Store(x);
  }

  // No-barrier variants that can be safely used in a few locations.
  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:
  // Array of length equal to the node height.  next_[0] is lowest level link.
  port::AtomicPointer next_[1];
};

这里, next_定义长度为1的port::AtomicPointer对象,其实是一个变长结构体,next_指向的是第一个。

其他的跟前面的秒速比较类似, 具体可以看代码。

参考资料:
skiplist论文

电源系列环路设计开关电源环路设计Flyback电源详细设计等资料98个合集(核心)一次电源环路数字控制培训(20090803李剑).ppt(核心)中文翻译-开关电源控制环路设计.pdf(核心)反激变换器的设计连载-反馈环路控制分析设计.pdf(核心)开关电源控制环路如何设计.doc(核心)开关电源控制环路设计 麦格米特.pdf(核心)开关电源环路中的TL431.doc(核心)开关电源环路设计(详细).pdf(核心)开关电源环路设计过程.pdf(核心)控制环路学习.docx(核心)环路稳定性分析.pdf(核心)环路补偿参数设计.xmcd(核心)电源工程师环路设计必备基础.pdf(核心)精确计算开关电源-环路.docx(核心TL431)开关电源环路设计要点.pdf(核心重点)开关电源环路设计计算.pdfBODE100-DC-DC-Stability-V2--电源环路分析--案例.pdfBuck变换器的环路设计(修改).docBUCK环路设计.pdfBuck电路环路设计仿真验证.pdfClosed-loop-Flyback-converter.pdfCMG大师关于环路稳定的叙述.pdfDC环路的稳定性问题.pdfflyback-design(0628).pptFlyback-loop-compensation.pdfflyback-small-signal.pdfFlyback电源详细设计.pdf【资料】多路输出反激式开关电源的反馈环路设计.doc一种DC-DC全区间分段线性斜坡补偿电路设计.pdf一种新型高精度斜坡补偿电路.pdf一种用于Boost+DC-DC转换器的新型动态斜坡补偿电路.pdf一种随占空比变化的分段线性斜坡补偿电路.pdf中文翻译-开关电源控制环路设计.pdf为何要环路补偿纠缠不休.pdf关于环路增益补偿理论.pdf关电源中斜坡补偿电路的分析设计[1].pdf单相光伏并网逆变器的环路控制.pdf反激开关电源中基于PC817ATL431配合的环路动态补偿设计.pdf反激式电源补偿网络设计探讨.pdf基于BUCK电路电压模式的反馈环路设计实例.pdf基于TL431的控制环路设计.xlsx基于UC3842的反激式开关电源的控制环路设计.pdf多环路稳定性分析.doc多路输出反激式开关电源的反馈环路设计.pdf多路输出反激式开关电源的反馈环路设计.Stamped.pdf如果你对环路补偿一知半解,看看这4大要点.pdf安捷伦开关电源环路测试.pdf实验调环路.docx开关变换器环路设计指南-从模拟到数字控制.pdf开关电源(Buck电路)的小信号模型及环路设计.pdf开关电源之环路稳定性.pdf开关电源控制环路设计.pdf开关电源控制环路设计要点.pdf开关电源控制环路设计,新手必看.pdf开关电源环路-反馈设计--电源反馈设计速成.pdf开关电源环路中的TL431.doc开关电源环路增益测试.pdf开关电源环路增益相位余量测量和稳定性分析.pdf开关电源环路控制之传递函数.pdf开关电源环路稳定性设计的基础理论知识.pdf开关电源环路稳定性设计的基础理论知识1.pdf开关电源环路稳定性设计的基础理论知识2.pdf开关电源环路设计(详细).pdf开关电源环路设计实例详解.pdf开关电源环路设计计算.pdf开关电源的控制环截止频率和开关频率的关系.docx开关电源的环路稳定性.pdf开关电源的环路补偿.pdf开关电源稳定性的设计测试.pdf控制环路学习.docx斜坡补偿到的来龙去脉实例.pdf最详细的开关电源反馈回路设计.docx深入环路分析的测试对比和解析.pdf环路学习笔记20180529.docx环路控制增益的基础.pdf环路控制学习.pdf环路控制学习总结-世纪电源网.pdf环路控制学习总结.pdf环路补偿很容易.pdf用示波器做环路分析需要几步.pdf电压环电流环设计.pdf电流型反激式开关电源的环路补偿设计.pdf电流型控制反激式开关电源补偿网络的优化设计.pdf电流控制技术和斜坡补偿.pdf电源反馈设计速成篇.pdf电源环路稳定性评价方法.pdf稳定的反馈环路对开关电源来说是非常重要的.doc精确计算开关电源-环路是如何计算出来的?.docx精确计算开关电源环路.pdf设计开关电源时PCB对电流环路的考虑.pdf资深工程师分享电源环路稳定性评价方法.pdf跟大师一起学习环路补偿.pdf造成开关电源环路不稳定的因素.pdf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值