levelDB Block
http://blog.csdn.net/sparkliang/article/details/8635821
BlockBuilder的接口
首先从Block的构建开始,这就是BlockBuilder类,来看下BlockBuilder的函数接口,一共有5个:
- void Reset(); // 重设内容,通常在Finish之后调用已构建新的block
- //添加k/v,要求:Reset()之后没有调用过Finish();Key > 任何已加入的key
- void Add(const Slice& key,const Slice& value);
- // 结束构建block,并返回指向block内容的指针
- Slice Finish();// 返回Slice的生存周期:Builder的生存周期,or直到Reset()被调用
- size_t CurrentSizeEstimate()const; // 返回正在构建block的未压缩大小—估计值
- bool empty() const { returnbuffer_.empty();} // 没有entry则返回true
主要成员变量如下:
- std::string buffer_; // block的内容
- std::vector<uint32_t> restarts_; // 重启点-后面会分析到
- int counter_; // 重启后生成的entry数
- std::string last_key_; // 记录最后添加的key
6.3.2 BlockBuilder::Add()
调用Add函数向当前Block中新加入一个k/v对{key, value}。函数处理逻辑如下:
S1 保证新加入的key > 已加入的任何一个key;
- assert(!finished_);
- assert(counter_ <= options_->block_restart_interval);
- assert(buffer_.empty() || options_->comparator->Compare(key,last_key_piece) > 0);
S2 如果计数器counter < opions->block_restart_interval,则使用前缀算法压缩key,否则就把key作为一个重启点,无压缩存储;
- Slice last_key_piece(last_key_);
- if (counter_ < options_->block_restart_interval) { //前缀压缩
- // 计算key与last_key_的公共前缀
- const size_t min_length= std::min(last_key_piece.size(), key.size());
- while ((shared < min_length)&& (last_key_piece[shared] == key[shared])) {
- shared++;
- }else{ // 新的重启点
- restarts_.push_back(buffer_.size());
- counter_ = 0;
- }
S3根据上面的数据格式存储k/v对,追加到buffer中,并更新block状态。
- const size_t non_shared = key.size() - shared; // key前缀之后的字符串长度
- // append"<shared><non_shared><value_size>" 到buffer_
- PutVarint32(&buffer_, shared);
- PutVarint32(&buffer_, non_shared);
- PutVarint32(&buffer_, value.size());
- // 其后是前缀之后的字符串 + value
- buffer_.append(key.data() + shared, non_shared);
- buffer_.append(value.data(), value.size());
- // 更新状态 ,last_key_ = key及计数器counter_
- last_key_.resize(shared); // 连一个string的赋值都要照顾到,使内存copy最小化
- last_key_.append(key.data() + shared, non_shared);
- assert(Slice(last_key_) == key);
- counter_++;
6.3.3 BlockBuilder::Finish()
调用该函数完成Block的构建,很简单,压入重启点信息,并返回buffer_,设置结束标记finished_:
- for (size_t i = 0; i < restarts_.size(); i++) { // 重启点
- PutFixed32(&buffer_, restarts_[i]);
- }
- PutFixed32(&buffer_, restarts_.size()); // 重启点数量
- finished_ = true;
- return Slice(buffer_);
6.3.4 BlockBuilder::Reset() & 大小
还有Reset和CurrentSizeEstimate两个函数,Reset复位函数,清空各个信息;函数CurrentSizeEstimate返回block的预计大小,从函数实现来看,应该在调用Finish之前调用该函数。
- void BlockBuilder::Reset() {
- buffer_.clear(); restarts_.clear(); last_key_.clear();
- restarts_.push_back(0); // 第一个重启点位置总是 0
- counter_ = 0;
- finished_ = false;
- }
- size_t BlockBuilder::CurrentSizeEstimate () const {
- // buffer大小 +重启点数组长度 + 重启点长度(uint32)
- return (buffer_.size() + restarts_.size() * sizeof(uint32_t) + sizeof(uint32_t));
- }
Block的构建就这些内容了,下面开始分析Block的读取,就是类Block。
6.3.5 Block类接口
对Block的读取是由类Block完成的,先来看看其函数接口和关键成员变量。
Block只有两个函数接口,通过Iterator对象,调用者就可以遍历访问Block的存储的k/v对了;以及几个成员变量,如下:
- size_t size() const { returnsize_; }
- Iterator* NewIterator(constComparator* comparator);
- const char* data_; // block数据指针
- size_t size_; // block数据大小
- uint32_t restart_offset_; // 重启点数组在data_中的偏移
- bool owned_; //data_[]是否是Block拥有的
6.3.6 Block初始化
Block的构造函数接受一个BlockContents对象contents初始化,BlockContents是一个有3个成员的结构体。
- >data = Slice();
- >cachable = false; // 无cache
- >heap_allocated = false; // 非heap分配
- 根据contents为成员赋值
- data_ = contents.data.data(), size_ =contents.data.size(),owned_ = contents.heap_allocated;
然后从data中解析出重启点数组,如果数据太小,或者重启点计算出错,就设置size_=0,表明该block data解析失败.
- if (size_ < sizeof(uint32_t)){
- size_ = 0; // 出错了
- } else {
- restart_offset_ = size_ - (1 +NumRestarts()) * sizeof(uint32_t);
- if (restart_offset_ > size_- sizeof(uint32_t)) size_ = 0;
- }
NumRestarts()函数就是从最后的uint32解析出重启点的个数,并返回:
return DecodeFixed32(data_ +size_ - sizeof(uint32_t))
6.3.7 Block::Iter
这是一个用以遍历Block内部数据的内部类,它继承了Iterator接口。函数NewIterator返回Block::Iter对象:return new Iter(cmp, data_,restart_offset_, num_restarts);
下面我们就分析Iter的实现。
主要成员变量有:
- const Comparator* constcomparator_; // key比较器
- const char* const data_; // block内容
- uint32_t const restarts_; // 重启点(uint32数组)在data中的偏移
- uint32_t const num_restarts_; // 重启点个数
- uint32_t current_; // 当前entry在data中的偏移. >= restarts_表明非法
- uint32_t restart_index_; // current_所在的重启点的index
下面来看看对Iterator接口的实现,简单函数略过。
>首先是Next()函数,直接调用private函数ParseNextKey()跳到下一个k/v对,函数实现如下:
S1 跳到下一个entry,其位置紧邻在当前value_之后。如果已经是最后一个entry了,返回false,标记current_为invalid。
- current_ = NextEntryOffset(); // (value_.data() + value_.size()) - data_
- const char* p = data_ +current_;
- const char* limit = data_ +restarts_; // Restarts come right after data
- if (p >= limit) { // entry到头了,标记为invalid.
- current_ = restarts_;
- restart_index_ =num_restarts_;
- return false;
- }
S2 解析出entry,解析出错则设置错误状态,记录错误并返回false。解析成功则根据信息组成key和value,并更新重启点index。
- uint32_t shared, non_shared,value_length;
- p = DecodeEntry(p, limit,&shared, &non_shared, &value_length);
- if (p == NULL || key_.size()< shared) {
- CorruptionError();
- return false;
- } else { // 成功
- key_.resize(shared);
- key_.append(p, non_shared);
- value_ = Slice(p +non_shared, value_length);
- while (restart_index_ + 1< num_restarts_ && GetRestartPoint(restart_index_ + 1) < current_) {
- ++restart_index_; //更新重启点index
- }
- return true;
- }
函数DecodeEntry从字符串[p, limit)解析出key的前缀长度、key前缀之后的字符串长度和value的长度这三个vint32值,代码很简单。
函数CorruptionError将current_和restart_index_都设置为invalid状态,并在status中设置错误状态。
函数GetRestartPoint从data中读取指定restart index的偏移值restart[index],并返回:DecodeFixed32(data_ + restarts_ +index * sizeof(uint32_t);
>接下来看看Prev函数,Previous操作分为两步:首先回到current_之前的重启点,然后再向后直到current_,实现如下:
S1首先向前回跳到在current_前面的那个重启点,并定位到重启点的k/v对开始位置。
- const uint32_t original =current_;
- while (GetRestartPoint(restart_index_)>= original) {
- if (restart_index_ == 0) { // 到第一个entry了,标记invalid状态
- current_ = restarts_;
- restart_index_ =num_restarts_;
- return;
- }
- restart_index_--;
- }
- SeekToRestartPoint(restart_index_);//根据restart index定位到重启点的k/v对
S2 第二步,从重启点位置开始向后遍历,直到遇到original前面的那个k/v对。
do {} while (ParseNextKey() &&NextEntryOffset() < original);
说说上面遇到的SeekToRestartPoint函数,它只是设置了几个有限的状态,其它值将在函数ParseNextKey()中设置。感觉这有点tricky,这里的value_并不是k/v对的value,而只是一个指向k/v对起始位置的0长度指针,这样后面的ParseNextKey函数将会取出重启点的k/v值。
- void SeekToRestartPoint(uint32_tindex) {
- key_.clear();
- restart_index_ = index;
- // ParseNextKey()会设置current_;
- //ParseNextKey()从value_结尾开始, 因此需要相应的设置value_
- uint32_t offset =GetRestartPoint(index);
- value_ = Slice(data_ + offset,0); // value长度设置为0,字符串指针是data_+offset
- }
> SeekToFirst/Last,这两个函数都很简单,借助于前面的SeekToResartPoint函数就可以完成。
- virtual void SeekToFirst() {
- SeekToRestartPoint(0);
- ParseNextKey();
- }
- virtual void SeekToLast() {
- SeekToRestartPoint(num_restarts_ - 1);
- while (ParseNextKey()&& NextEntryOffset() < restarts_) {} //Keep skipping
- }
> 最后一个Seek函数,跳到指定的target(Slice),函数逻辑如下:
S1 二分查找,找到key < target的最后一个重启点,典型的二分查找算法,代码就不再贴了。
S2 找到后,跳转到重启点,其索引由left指定,这是前面二分查找到的结果。如前面所分析的,value_指向重启点的地址,而size_指定为0,这样ParseNextKey函数将会取出重启点的k/v值。
SeekToRestartPoint(left);
S3 自重启点线性向下,直到遇到key>= target的k/v对。
- while (true) {
- if (!ParseNextKey()) return;
- if (Compare(key_, target)>= 0) return;
- }
上面就是Block::Iter的全部实现逻辑,这样Block的创建和读取遍历都已经分析完毕。
levelDB Block的更多相关文章
- LevelDB源码分析-sstable的Block
sstable中的Block(table/block.h table/block.cc table/block_builder.h table/block_builder.cc) sstable中的b ...
- leveldb源码分析--SSTable之block
在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSi ...
- leveldb 学习记录(六)SSTable:Block操作
block结构示意图 sstable中Block 头文件如下: class Block { public: // Initialize the block with the specified con ...
- leveldb 性能、使用场景评估
最近有个业务写远远大于读,读也集中在最近写入,这不很适合采用leveldb存储么,leveldb业界貌似ssdb用得挺广,花了两天时间就ssdb简单做下测试,以下总结. ssdb 是leveldb的r ...
- LevelDB库简介
LevelDB库简介 一.LevelDB入门 LevelDB是Google开源的持久化KV单机数据库,具有很高的随机写,顺序读/写性能,但是随机读的性能很一般,也就是说,LevelDB很适合应用在查询 ...
- leveldb - log格式
log文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据.因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及D ...
- leveldb - sstable格式
整体上,sstable文件分为数据区与索引区,尾部的footer指出了meta index block与data index block的偏移与大小,data index block指出了各data ...
- LevelDB(v1.3) 源码阅读之 Arena(内存管理器)
LevelDB(v1.3) 源码阅读系列使用 LevelDB v1.3 版本的代码,可以通过如下方式下载并切换到 v1.3 版本的代码: $ git clone https://github.com/ ...
- LevelDB源码分析--使用Iterator简化代码设计
我们先来参考来至使用Iterator简化代码2-TwoLevelIterator的例子,略微修改希望能帮助更加容易立即,如果有不理解请各位看客阅读原文. 下面我们再来看一个例子,我们为一个书店写程序, ...
随机推荐
- laplace transform 拉普拉斯变换
参考网址: 1. https://en.wikipedia.org/wiki/First-hitting-time_model 2. https://en.wikipedia.org/wiki/Lap ...
- mepg
MPEG(Moving Picture Experts Group,动态图像专家组)
- 基于VUE多人聊天项目
项目背景 公司平台要做一个通讯系统,本来是java 来做的后面改前端+PHP来做,所以就用VUE来做这个了. github github地址 新人求star 技术栈 vue-axios vuex we ...
- java:Set对象TreeSet有序子类,HashSet无序子类,重复对象二
TreeSet有序子类; HashSet无序子类 重复重复元素,Object对象是通过equals和hashCode来进行过滤的. 如果将上一篇提到中的例子中的TreeSet,换成HashSet,那么 ...
- day3 ord,chr,random,string
day3复习 >>> for i in range(10): ... if i == 3: ... break ... print(i) ... 0 1 2 >>> ...
- react native之使用 Fetch进行网络数据请求
这是一个单独的页面,可以从其他地方跳转过来. 输入语言关键字,从github检索相关数据 import React, {Component} from 'react'; import { StyleS ...
- 求大师点化,寻求大文件(最大20G左右)上传方案
之前仿造uploadify写了一个HTML5版的文件上传插件,没看过的朋友可以点此先看一下~得到了不少朋友的好评,我自己也用在了项目中,不论是用户头像上传,还是各种媒体文件的上传,以及各种个性的业务需 ...
- HDU 5687 Problem C ( 字典树前缀增删查 )
题意 : 度熊手上有一本神奇的字典,你可以在它里面做如下三个操作: 1.insert : 往神奇字典中插入一个单词 2.delete: 在神奇字典中删除所有前缀等于给定字符串的单词 3.search: ...
- 嵌入式逻辑分析仪SignalTap II 设计范例
Crazy Bingo :嵌入式逻辑分析仪SignalTap II 设计范例 例程下载地址 http://www.cnblogs.com/crazybingo/archive/2011/07/26/ ...
- (42)嵌入式项目中常用到的C语言技能总结
嵌入式项目中常用到的C语言技能 1.指针 .结构体. 枚举. 联合.数组.字符串.链表七个专题 2.结构体指针.结构体的多重嵌套[结构体中嵌套结构体.结构体中嵌套枚举.联合体.结构体中嵌套函数指针.一 ...