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. 首先是查询,如果我们要找到内容为 29
的 key
- 首先 skiplist
有一个变量 head_
始终指向第一个节点的最上层,也就是图中最上层的 head_
- 29
和第 4
层 head_
的 next
节点 7
比较,大于 7
,此时头节点指向第 4
层的 7
- 29
和第 4
层 7
的 next
节点 31
比较,小于 31
,降一层
- 29
和第 3
层 7
的 next
节点 31
比较,小于 31
,降一层
- 29
和第 2
层 7
的 next
节点 15
比较,大于 15
,此时头节点指向第 2
层的 15
- 29
和第 2
层 15
的 next
节点 31
比较,小于 31
,降一层
- 29
和第 1
层 15
的 next
节点 24
比较,大于 24
,此时头节点指向第 1
层的 24
- 29
和第 1
层 24
的 next
节点 29
比较,等于 29
,且是第一层,没错就是你了
在上图中插入
30
- 首先得到一个随机值(
1
到kMaxHeight
之间)来设置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
中有两个变量,key
和 next_
数组,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/ 】
- Leveldb源码解析第五篇【memtable之skiplist】
- memtable-levelDB源码解析
- Leveldb源码解析第六篇【memtable】
- LevelDB源码之SkipList原理LevelDB源码之SkipList原理
- LevelDB源码剖析之Memtable(1)
- LevelDB源码剖析之Memtable(2)
- LevelDB源码之SkipList原理
- LevelDB源码之SkipList原理
- LevelDB源码之SkipList原理
- LevelDB源码剖析之SkipList
- LevelDB源码剖析之SkipList
- levelDB源码分析-Memtable
- leveldb源码阅读-memtable
- leveldb源码剖析--MemTable
- LevelDB源码分析9-MemTable
- levelDB源码分析-Skiplist
- LevelDb之五:MemTable详解
- leveldb设计分析之memtable
- 软件架构风格
- 【C++】【LeetCode】27. Remove Element
- [转载觉得写的不错]Node.js后端架构的思考
- inline-block 元素间存在空隙
- 改变TabLayout的线的宽度
- Leveldb源码解析第五篇【memtable之skiplist】
- IMWeb提升营 | 第三次课堂直播笔记:细解鹅厂面试题
- 需要同时启动多个tomcat的配置
- gulp:入门简介
- 设备中断绑定到特定CPU(SMP IRQ Affinity)
- 【CV】像素当量的标定
- 项目中的大改动
- S5pv210 HDMI 接口在 Linux 3.0.8 驱动框架解析 (By liukun321 咕唧咕唧)
- produces在@requestMapping中的使用方式和作用