LevelDB 有两个地方需要用到有序遍历:
通过前面的文章,我们了解到 LevelDB 的数据是保存在内部多个不同组件的,并且每个组件的数据格式都不一样。
LevelDB 通过在每一个组件上实现一套相同的迭代器接口来屏蔽掉每个组件的实现细节。
再通过这些迭代器的组合,提供完整的有序遍历能力。
应用程序可以通过调用 leveldb::DB::NewIterator 来创建一个迭代器。
virtual Iterator* NewIterator(const ReadOptions& options) = 0;
NewIterator 返回一个 leveldb::Iterator 指针,这个指针在使用之后需要 delete 掉。
Iterator 提供了和遍历数据相关的接口:
这里要注意,level0 的 TwoLevelIterator 和 level1~n 的 TwoLevelIterator 是不一样的。
简单看下 MergingIterator 如何合并多个迭代器,实现有序遍历的。
virtual void Seek(const Slice& target) {
for (int i = 0; i < n_; i++) {
children_[i].Seek(target);
}
FindSmallest();
direction_ = kForward;
}
Seek(target) 的作用是:定位到第一个大于等于 target 的 key。所以,需要将内部每个迭代器都定位到各自的第一个大于等于 target 的 key,再找出其中最小的,就是全局第一个大于等于 target 的 key。这个过程可能产生多次 I/O。
virtual void Next() {
assert(Valid());
// 简单起见,忽略掉 Forward <=> Backward 的转变...
current_->Next();
FindSmallest();
}
current 就是指向当前目标值的迭代器。Next() 的作用是定位到下一个比 current 指向的目标大的 key。
virtual void Prev() {
assert(Valid());
// 简单起见,忽略掉 Forward <=> Backward 的转变...
current_->Prev();
FindLargest();
}
Prev() 的作用与 Next() 相反。
简单说:Prev 的性能比 Next 差。
因为 LevelDB 维护的有序数据是单向的。每次 Prev 都需要使用类似二分查找的方式定位数据;而 Next 基本上只需要取相邻的下一个值。
具体可以参考我以前写的一篇文章:leveldb iterator 的 Prev 究竟比 Next 差在哪?