跳跃表的具体原理,请参考: www.huliujia.com/blog/071730…
SkipList
SkipList是LevelDB的MemTable的底层存储结构。MemTable不会进行删除元素,所以在SkipList中没有删除元素的接口。首先看SkipList对外开放接口:
template <typename Key, class Comparator>
class SkipList {
private:
struct Node;
public:
explicit SkipList(Comparator cmp, Arena* arena);
SkipList(const SkipList&) = delete;
SkipList& operator=(const SkipList&) = delete;
void Insert(const Key& key);
bool Contains(const Key& key) const;
private:
// 比较器
Comparator const compare_;
Arena* const arena_; // Arena used for allocations of nodes
// skiplist 的前置哨兵节点
Node* const head_;
};
首先看它的模板信息,其中 Key 是存储的元素类型,Comparator是元素比较器。
SkipList对外有两个接口:
// 插入操作
void Insert(const Key& key);
// 查看key是否存在
bool Contains(const Key& key) const;
本篇详细讲解一下插入接口。在讲解 Insert 接口前,我们需要了解,SkipList插入的数据是Key类型,那么在SkipList中是如何存储的呢?
跳跃表的每个元素存储在 Node 结构中,Node之间相互链接就形成了跳跃表。在 SkipList 类中维护跳跃表的头节点 head_。我们可以通过 head_找到跳跃表中任何一个元素。
Node 类实现如下:
template <typename Key, class Comparator>
struct SkipList<Key, Comparator>::Node {
explicit Node(const Key& k) : key(k) {}
// 存储插入到跳跃表的元素
Key const key;
// 获取该节点,后面的第n层节点
Node* Next(int n);
// 设置 该节点在第n层的后置节点为 x
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].store(x, std::memory_order_release);
}
// 获取该节点在第n层的后置节点
Node* NoBarrier_Next(int n) {
assert(n >= 0);
return next_[n].load(std::memory_order_relaxed);
}
//// 设置 该节点在第n层的后置节点为 x
void NoBarrier_SetNext(int n, Node* x) {
assert(n >= 0);
next_[n].store(x, std::memory_order_relaxed);
}
private:
// // 指针数组的长度即为该节点的 level,next_[0] 是最低层指针.
// Array of length equal to the node height. next_[0] is lowest level link.
std::atomic<Node*> next_[1]; // 指向指针数组的指针
};
每个Node节点中维护一个 next_[kMaxHeight],每个数组元素为 Node*,指向该层下一个节点。
我认为levelDB中SkipList采用如此结构,可以节省内存空间,所有的元素只需要存储一次,利用next_数组维护层结构。
注意:在初始化一个新的Node节点时,levelDB采用了“定位构造的方法”,在Node类声明中先将 next_数组大小设置为1,其实这个1,只是起了一个占位的作用,实际大小是在构造函数中确定的。
template <typename Key, class Comparator>
typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::NewNode(
const Key& key, int height) {
// 对 柔性数组(std::atomic<Node*> next_[1];) 进行申请内存
char* const node_memory = arena_->AllocateAligned(
sizeof(Node) + sizeof(std::atomic<Node*>) * (height - 1));
// 定位构造
// placement new就是在用户指定的内存位置上构建新的对象,
// 这个构建过程不需要额外分配内存,只需要调用对象的构造函数即可
return new (node_memory) Node(key);
}