大白话解析LevelDB: Block Iterator

2024-02-22 01:20

本文主要是介绍大白话解析LevelDB: Block Iterator,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Block Iterator
    • Iterator 接口
    • Block Iterator 的实现
      • Block Iterator 的私有成员
      • Block Iterator 的构造函数
      • Block::Iter::Valid()
      • Block::Iter::status()
      • Block::Iter::key()
      • Block::Iter::value()
      • Block::Iter::Next()
      • Block::Iter::Prev()
      • Block::Iter::Seek(const Slice& target)
      • Block::Iter::SeekToFist()
      • Block::Iter::SeekToLast()

Block Iterator

Block Iterator用于遍历一个SST中的某个BlockBlock的结构如下:

+----------------+
|    Key1:Value1 |
+----------------+
|    Key2:Value2 |
+----------------+
|       ...      |
+----------------+
|    KeyN:ValueN |
+----------------+
| Restart Point1 |
+----------------+
| Restart Point2 |
+----------------+
|       ...      |
+----------------+
| Restart PointM |
+----------------+
| Num of Restarts|
+----------------+

关于Block的结构详情可移步参考(大白话解析LevelDB: BlockBuilder)[https://blog.csdn.net/sinat_38293503/article/details/134997464#Block__153]。

Iterator 接口

Block::IterIterator接口的一种实现,所以我们先看下Iterator里都有哪些接口需要实现:

class LEVELDB_EXPORT Iterator {public:Iterator();Iterator(const Iterator&) = delete;Iterator& operator=(const Iterator&) = delete;virtual ~Iterator();// 判断迭代器当前所在位置是否有效,如果有效,// 则可以通过 key() 和 value() 获取当前键值对。virtual bool Valid() const = 0;// 将当前位置移动到第一个 Key-Value 所在处。virtual void SeekToFirst() = 0;// 将当前位置移动到最后的 Key-Value 所在处。virtual void SeekToLast() = 0;// 将当前位置移动到第一个大于等于 target 的 Key-Value 所在处。virtual void Seek(const Slice& target) = 0;// 将当前位置移动到下一个 Key-Value 所在处。virtual void Next() = 0;// 将当前位置移动到上一个 Key-Value 所在处。virtual void Prev() = 0;// 返回当前位置的 Key。virtual Slice key() const = 0;// 返回当前位置的 Value。virtual Slice value() const = 0;// 返回迭代器的当前状态。// 如果状态不是 ok,则说明迭代器已经失效,不可使用了。virtual Status status() const = 0;// 用户可注册多个 CleanupFunction,当迭代器被销毁时,会按顺序调用这些 // CleanupFunction。// 需要注意的是,RegisterCleanup 这个方法不需要 Iterator 的子类实现,// Iterator 已经实现了,用户不需要重写。using CleanupFunction = void (*)(void* arg1, void* arg2);void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
};

Block Iterator 的实现

Block::IterIterator接口的一种实现,它需要实现Iterator接口里的所有抽象方法。

Block Iterator 的私有成员

在阅读Block Iterator的实现之前,我们先来看一下Block Iterator里各个私有成员的含义,为后续的阅读做好准备。

class Block::Iter : public Iterator {private:// 构造 Iter 的时候传入一个 Comparator,决定了 Block 中的 key 的排序方式const Comparator* const comparator_;// data_ 指向存放 Block 数据的地方const char* const data_;       // underlying block contents// 通过 data_ + restarts_ 可得到重启点数组的起始位置uint32_t const restarts_;      // Offset of restart array (list of fixed32)// 重启点的数量uint32_t const num_restarts_;  // Number of uint32_t entries in restart array// current_ is offset in data_ of current entry.  >= restarts_ if !Valid//// 当前 Key-Value 在 data_ 中的偏移量uint32_t current_;// 当前处在哪个重启点,通过 restarts_[restart_index_] 可拿到当前重启点uint32_t restart_index_;  // Index of restart block in which current_ falls// 当前 Key-Value 中的 keystd::string key_;// 当前 Key-Value 中的 valueSlice value_;// 当前 Iterator 的状态Status status_;
}

Block Iterator 的构造函数

SST中解析出一个Block的以下 3 个信息后,就可以构造一个Block Iterator了:

  • data指针
  • 重启点数组在data中的偏移量
  • 重启点的数量
Iter(const Comparator* comparator, const char* data, uint32_t restarts, uint32_t num_restarts): comparator_(comparator),data_(data),restarts_(restarts),num_restarts_(num_restarts),current_(restarts_),restart_index_(num_restarts_) {assert(num_restarts_ > 0);
}

Block::Iter::Valid()

current_表示当前Key-Valuedata_中的偏移量,restarts_表示重启点数组在data_中的偏移量,所以current_小于restarts_时,Block::Iter是有效的。

如果current_大于等于restarts_current_就已经指向重启点的区域了,此时Block::Iter是无效的。

bool Valid() const override { return current_ < restarts_; }

Block::Iter::status()

没啥好说的,直接返回当前Iterator的状态。

Status status() const override { return status_; }

Block::Iter::key()

Prev(), Next(), Seek()方法会将current_指向一个有效的Key-Value,并且将该Key-Valuekeyvalue分别存储到key_value_中,所以key()方法直接返回key_即可。

Slice key() const override {assert(Valid());return key_;
}

Block::Iter::value()

Prev(), Next(), Seek()方法会将current_指向一个有效的Key-Value,并且将该Key-Valuekeyvalue分别存储到key_value_中,所以key()方法直接返回key_即可。

Slice value() const override {assert(Valid());return value_;
}

Block::Iter::Next()

将迭代器移动到下个Key-Value的位置。

void Next() override {assert(Valid());// 将 current_ 移动到下一个 Key-Value 的位置,// 并且解析出 Key。ParseNextKey();
}

Next()的实现逻辑都甩给了ParseNextKey().

ParseNextKey()的实现如下:

bool ParseNextKey() {// 计算下一个 Key-Value 的偏移量current_ = NextEntryOffset();// 找到下一个 Key-Value 的首地址const char* p = data_ + current_;// limit 指向重启点数组的首地址const char* limit = data_ + restarts_;  // Restarts come right after data// 如果当前 Key-Value 的偏移量超过了重启点数组的首地址,// 表示已经没有 Next Key 了。if (p >= limit) {current_ = restarts_;restart_index_ = num_restarts_;return false;}// 根据重启点来解析下一个 Key-Valueuint32_t shared, non_shared, value_length;p = DecodeEntry(p, limit, &shared, &non_shared, &value_length);if (p == nullptr || key_.size() < shared) {// 解析失败,将错误记录到 Iterator 的 status_ 里。CorruptionError();return false;} else {// 解析成功,此时 p 指向了 Key 的 non_shared 部分,// 将 shared 和 non_shared 部分拼接起来,得到当前 Key。key_.resize(shared);key_.append(p, non_shared);value_ = Slice(p + non_shared, value_length);// 更新 restart_index_,找到下一个重启点while (restart_index_ + 1 < num_restarts_ &&GetRestartPoint(restart_index_ + 1) < current_) {++restart_index_;}return true;}
}

Block::Iter::Prev()

Prev()Next()实现不一样,不能简单的将current往上移一个位置。

见下图,假设current_指向Key3,上一个KeyKey2Key2Key3里都没有存储完整的KeyKey3需要依赖Key2来解析出完整的Key3,而Key2需要依赖Key1来解析出完整的Key2,以此类推,Key1需要依赖Key0来解析出完整的Key1,而Key0位于重启点,是没有依赖的,自己本身就存储了完整的Key

所以要找到Key2,我们需要先找到Key3之前最近的一个重启点,也就是RestartPoint0

然后将current_移动到该重启点的位置,再不断的向后边移动current_,边解析出当前Key,直到current_指向Key3的前一个Key,也就是Key2

                              current_+|v+--------+--------+--------+--------+--------+--------+|  Key0  |  Key1  |  Key2  |  Key3  |  Key4  |  Key5  |+--------+--------+--------+--------+--------+--------+^                                   ^|                                   |+                                   +
RestartPoint0                       RestartPoint1
void Prev() override {assert(Valid());// 把 restart_index_ 移动到 current_ 之前最近的一个重启点。const uint32_t original = current_;while (GetRestartPoint(restart_index_) >= original) {if (restart_index_ == 0) {// 前面没有 Key 了current_ = restarts_;restart_index_ = num_restarts_;return;}restart_index_--;}// 把 current_ 移动到重启点的位置,SeekToRestartPoint(restart_index_);do {// 将 current_ 不断向后移动,一直移动到原先 current_ 的前一个位置。} while (ParseNextKey() && NextEntryOffset() < original);
}

Block::Iter::Seek(const Slice& target)

理解Prev()的实现后,Seek()的实现就比较容易理解了。我们不能直接去找target的位置,而是需要先找到target的那个重启点。然后再从这个重启点开始,一步步向右移动current_,直到找到第一个大于等于targetKey

而找重启点的过程,是一个二分查找的过程。

void Seek(const Slice& target) override {// 理解 Prev() 的实现后,Seek() 的实现就比较容易理解了。// 我们不能直接去找 target 的位置,而是需要先找到 target // 的那个重启点。用二分的方式查找这个重启点。uint32_t left = 0;uint32_t right = num_restarts_ - 1;int current_key_compare = 0;if (Valid()) {// 如果当前 Iterator 已经指向某个有效的 Key 了,我们就可以利用这个 Key 来把// 二分查找 target 重启点的范围缩小一些。current_key_compare = Compare(key_, target);if (current_key_compare < 0) {// 如果当前 Key 比 target 小,那么 target 重启点的位置一定在当前重启点的// 右边,包括当前重启点。left = restart_index_;} else if (current_key_compare > 0) {// 如果当前 Key 比 target 大,那么 target 重启点的位置一定在当前重启点的// 左边,包括当前重启点。right = restart_index_;} else {// 如果当前 Key 已经是 target 了,那么就不需要再做其他操作了。return;}}// 二分查找 target 的重启点。while (left < right) {// 找到 left 和 right 中间的那个重启点,看这个重启点的 Key 和 target 的大小关系。//   - 如果 mid 重启点的 Key 比 target 小,那么 target 重启点的位置一定在 mid //     的右边,需要吧 left 移动到 mid 的位置。//   - 如果 mid 重启点的 Key 比 target 大,那么 target 重启点的位置一定在 mid//     的左边,需要把 right 移动到 mid 的位置。    uint32_t mid = (left + right + 1) / 2;uint32_t region_offset = GetRestartPoint(mid);uint32_t shared, non_shared, value_length;const char* key_ptr = DecodeEntry(data_ + region_offset, data_ + restarts_, &shared,&non_shared, &value_length);if (key_ptr == nullptr || (shared != 0)) {CorruptionError();return;}Slice mid_key(key_ptr, non_shared);if (Compare(mid_key, target) < 0) {left = mid;} else {right = mid - 1;}}// 找到 target 的重启点后,一步步向右移动 current_,直到找到第一个大于等于// target 的 Key。assert(current_key_compare == 0 || Valid());bool skip_seek = left == restart_index_ && current_key_compare < 0;if (!skip_seek) {SeekToRestartPoint(left);}while (true) {if (!ParseNextKey()) {return;}if (Compare(key_, target) >= 0) {return;}}
}

Block::Iter::SeekToFist()

void SeekToFirst() override {// 移动到一个重启点。SeekToRestartPoint(0);// 解析出第一个 Key。ParseNextKey();
}

Block::Iter::SeekToLast()

void SeekToLast() override {// 移动到最后一个重启点。SeekToRestartPoint(num_restarts_ - 1);// 不断向后移动 current_,直到最后一个 Key。while (ParseNextKey() && NextEntryOffset() < restarts_) {}
}

这篇关于大白话解析LevelDB: Block Iterator的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/733714

相关文章

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

python解析HTML并提取span标签中的文本

《python解析HTML并提取span标签中的文本》在网页开发和数据抓取过程中,我们经常需要从HTML页面中提取信息,尤其是span元素中的文本,span标签是一个行内元素,通常用于包装一小段文本或... 目录一、安装相关依赖二、html 页面结构三、使用 BeautifulSoup javascript

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动