skiplist 原理介绍

skiplist 由William Pugh 在论文Skip Lists: A Probabilistic Alternative to Balanced Trees 中提出的一种数据结构,skiplist 是一种随机化存储的多层线性链表结构,插入,查找,删除的都是对数级别的时间复杂度。skiplist 和平衡树有相同的时间复杂度,但相比平衡树,skip实现起来更简单。

下图是wikipedia 上一个一个高度为4的skiplist

从垂直角度看,skiplist 的第0层以单链表的形式按照从小到大的顺序存储全部数据,越高层的链表的节点数越少,这样的特点实现了skiplist 在定位某个位置时,通过在高层较少的节点中查找就可以确定需要定位的位置处于哪个区间,从高层到低层不断缩小查找区间。以上图为例,比如我们需要在skiplist中查找2,查找过程如下,首先在最高层确定到2只可能处于1->NULL 这个区间,然后在第三层查找确定 2 只可能处于 1->4 这个区间,继续在第二层查找确定2 只可能处于1-3 这区间,最后在最底层1->3 这个区间查找可以确定2 是否存在于skiplist之中。
下图是wikipedia上提供的表示skiplist插入过程的一张gif,此图形象的说明了skiplist 定位以及插入节点的过程。

从水平角度来看,skiplist实现在链表开始的时候设置名为head 的哨兵节点,每一层链表的结束为止全部指向NULL。

leveldb 实现

leveldb 实现的skiplist位于db/skiplist.h。

skiplist Node 类型定义

// Implementation details follow
template<typename Key, class Comparator>
struct SkipList<Key,Comparator>::Node {explicit Node(const Key& k) : key(k) { }// Node 存储的内容Key const key;// Accessors/mutators for links.  Wrapped in methods so we can// add the appropriate barriers as necessary.// 获取当前节点在指定level的下一个节点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());}// 将当前节点在指定level的下一个节点设置为xvoid 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);}// 无内存屏障版本set。关于leveldb 内存屏障在新一篇博客介绍// 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];
};

skiplist 类成员变量

private:// 使用枚举类型定义skiplist 最高高度enum { kMaxHeight = 12 };// Immutable after construction// 用户定制的比较器Comparator const compare_;// leveldb 实现的简单的内存分配器Arena* const arena_;    // Arena used for allocations of nodes// skiplist 的前置哨兵节点Node* const head_;// Modified only by Insert().  Read racily by readers, but stale// values are ok.// 记录当前skiplist使用的最高高度port::AtomicPointer max_height_;   // Height of the entire list

skiplist 插入

template<typename Key, class Comparator>
void SkipList<Key,Comparator>::Insert(const Key& key) {// TODO(opt): We can use a barrier-free variant of FindGreaterOrEqual()// here since Insert() is externally synchronized.// 声明prev节点,代表插入位置的前一个节点Node* prev[kMaxHeight];// 使用FindGreaterOrEqual函数找到第一个大于等于插入key的位置Node* x = FindGreaterOrEqual(key, prev);// Our data structure does not allow duplicate insertionassert(x == NULL || !Equal(key, x->key));// 使用随机数获取该节点的插入高度int height = RandomHeight();if (height > GetMaxHeight()) {// 大于当前skiplist 最高高度的话,将多出的来的高度的prev 设置为哨兵节点for (int i = GetMaxHeight(); i < height; i++) {prev[i] = head_;}//fprintf(stderr, "Change height from %d to %d\n", max_height_, height);// It is ok to mutate max_height_ without any synchronization// with concurrent readers.  A concurrent reader that observes// the new value of max_height_ will see either the old value of// new level pointers from head_ (NULL), or a new value set in// the loop below.  In the former case the reader will// immediately drop to the next level since NULL sorts after all// keys.  In the latter case the reader will use the new node.
// 跟新max_height_    max_height_.NoBarrier_Store(reinterpret_cast<void*>(height));}// 创建要插入的节点对象x = NewNode(key, height);for (int i = 0; i < height; i++) {// NoBarrier_SetNext() suffices since we will add a barrier when// we publish a pointer to "x" in prev[i].// 首先将x的next 指向prev 的下一个节点x->NoBarrier_SetNext(i, prev[i]->NoBarrier_Next(i));// 将prev 指向xprev[i]->SetNext(i, x);}
}

skiplist 查找

template<typename Key, class Comparator>
bool SkipList<Key,Comparator>::Contains(const Key& key) const {// 找到大于等于当前key的第一个node,然后判断node 的key// 和传入的key 是否相等Node* x = FindGreaterOrEqual(key, NULL);if (x != NULL && Equal(key, x->key)) {return true;} else {return false;}
}

FindGreaterOrEqual

函数的作用是找到第一个大于或等于指定的key 的node,以及该node的前一个node

template<typename Key, class Comparator>
typename SkipList<Key,Comparator>::Node* SkipList<Key,Comparator>::FindGreaterOrEqual(const Key& key, Node** prev)const {Node* x = head_;// level 从0 开始编码int level = GetMaxHeight() - 1;while (true) {// 定位到当前level的下一个节点Node* next = x->Next(level);// key 没有在当前区间if (KeyIsAfterNode(key, next)) {// Keep searching in this listx = next;} else {// key 在当前区间,在低level 继续查找,// 在查找的同时设置prev 节点if (prev != NULL) prev[level] = x;// 在最低level找到相应位置if (level == 0) {return next;} else {// Switch to next listlevel--;}}}
}

RandomHeight

利用随机数实现每次有4分之一的概率增长高度。

template<typename Key, class Comparator>
int SkipList<Key,Comparator>::RandomHeight() {// Increase height with probability 1 in kBranchingstatic const unsigned int kBranching = 4;int height = 1;while (height < kMaxHeight && ((rnd_.Next() % kBranching) == 0)) {height++;}assert(height > 0);assert(height <= kMaxHeight);return height;
}

FindLessThan

template<typename Key, class Comparator>
typename SkipList<Key,Comparator>::Node*
SkipList<Key,Comparator>::FindLessThan(const Key& key) const {Node* x = head_;// 从最高level开始定位区间int level = GetMaxHeight() - 1;while (true) {assert(x == head_ || compare_(x->key, key) < 0);// 在当前level 查找Node* next = x->Next(level);// if 分支为true 的时候表示需要查找的位置在当前区间if (next == NULL || compare_(next->key, key) >= 0) {   // 在最后一层停止查找if (level == 0) {return x;} else {// Switch to next listlevel--;}} else {// 在当前level 就找到了比key 小的节点x = next;}}
}

总结

skiplist最底层单链表有序存储全部元素,利用多层有序链表的结构实现加速索引的功能,处于越高level 节点的链表越稀疏查找速度越快,在不断向下查找的过程中不断缩小查找空间。
总的来说,skiplist 是一种设计巧妙的数据结构,leveldb 的实现可读性高,容易理解。

Leveldb skiplist 实现及解析相关推荐

  1. C++之String的find方法,查找一个字符串在另一个字符串的什么位置;leveldb字符串转数字解析办法...

    由于leveldb基于key value,而且是根据字符串进行排序的.key 和value都是string类型的,对于我要处理的有许多数字,所以就要找一个C /C++解析文本的工具了. C 在这方面很 ...

  2. leveldb 原理解析

    目录 概览 Features 整体结构 Memtable Immutable Memtable SSTable 文件(SST) SSTable的物理结构 Block 物理结构 节省 key 占用空间 ...

  3. leveldb的sstable-ldb解析

    (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) leveldb数据库的sstable-ldb文件解析. 引言 ldb文件作为sorted-string- ...

  4. LevelDB源码解析(1) Arena内存分配器

    你也可以通过我的独立博客 -- www.huliujia.com 获取本篇文章 背景 LevelDB中需要频繁申请和释放内存,如果直接使用系统的new/delete或者malloc/free接口申请和 ...

  5. leveldb源码解析系列—Log

    文章目录 Log文件组织方式 Log文件的写入 AddRecord EmitPhysicalRecord Log文件的读取 SkipToInitialBlock ReadPhysicalRecord ...

  6. Redis源码解析:数据结构详解-skiplist

    跳表是个什么数据结构? 本文的很多内容参考自如下文章<Redis 为什么用跳表而不用平衡树?>,为了加深理解,所以用自己的话复述一遍. 如图所示,redis中的zset在元素少的时候用zi ...

  7. LSM Tree 学习笔记——MemTable通常用 SkipList 来实现

    最近发现很多数据库都使用了 LSM Tree 的存储模型,包括 LevelDB,HBase,Google BigTable,Cassandra,InfluxDB 等.之前还没有留意这么设计的原因,最近 ...

  8. HBase、Redis、MongoDB、Couchbase、LevelDB 五款主流NoSQL数据库大比拼

    在 HBase.Redis.MongoDB.Couchbase.LevelDB 五款较主流的数据库产品中,本文将主要对它们进行分析对比. 鉴于缺乏项目中的实战经验沉淀,本文内容和观点主要还是从各平台资 ...

  9. Leveldb二三事

    摘要 阅读这篇文章,希望你首先已经对Leveldb有了一定的了解,并预先知晓下列概念: LSM技术 跳表 WAL技术 Log Compaction 本文不是一篇专注于源代码解析的文章,也不是一篇Lev ...

最新文章

  1. c++ stl之pirority_queue
  2. 2019年的wps计算机考试题,2019年3月计算机一级WPS模拟题及答案(2.21)
  3. ret2dlresolve归纳
  4. 平流式初沉池贮砂斗计算_?初沉池、二沉池的作用与区别-亨孚科技
  5. 电子计算机专业211大学,这所高校不是211,但“计算机”实力远超985,被称“IT人才摇篮”...
  6. 第7章[7.22] Ext JS类的继承与混合
  7. Nginx反向代理及简单负载均衡配置
  8. Memcached 集群架构方面的问题
  9. Silverlight读取Zip文件中的图片与视频
  10. 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表
  11. 《Excel图表之道》——书和人
  12. 【亲测有效】iPhone实现定时关机、开机 - 远离手机 准时睡觉
  13. Selenium+IP爬虫刷新网页
  14. Unity3d报错:Error building Player: Win32Exception: ApplicationName='xxxxxx/zipalign.exe'
  15. 装饰者模式 增加功能;动态代理减少功能 只要完成自己部分功能 (繁杂部分交给他人处理)...
  16. c语言结构体简单试题,C语言6结构体练习题6
  17. kubernetes中Pod容器错误 init:ImagePullBackOff 解决方法
  18. SE壳C#程序-CrackMe-爆破 By:凉游浅笔深画眉 / Net7Cracker
  19. WPF制作贪吃蛇小游戏
  20. 16位rgb2gray算法verilog移植

热门文章

  1. Java—实现 RPG 人物生成器
  2. 计算机科技文化节宣传标语,科技文化节宣传口号
  3. 干货 | 阿里的图像搜索架构
  4. IT学习笔记--MySQL
  5. Java 基础-01 Java语言入门
  6. 程序员如何避免陷入内卷
  7. CPU诞生记|CPU制造全过程详解
  8. 数据采集有哪几种方法
  9. 小米mix2安兔兔html5跑分,小米2s跑分:小米mix2s安兔兔跑分结果怎么样
  10. 前端基础-什么是前端