class Block
class Block是Level DB里面的重要数据结构,该数据结构用来承载已经存储到文件中的数据。已经被存储的数据当需要再次加载、应用(例如搜索),这时首先要把数据加载、初始化到class Block里面。
数据的组织形式:
数据
数据的组织形式如图1所示。data表示指向数据的指针,size表示整个数据的大小。在数据组织上,数据里面采用了多个restart的结构,每个restart有一个完整的key字符串序列,在restart后续的数据,会采用和前一个key值差值的方式存储。在若干个key-value数据后,又会有一个新的restart数据。整个value最后一个4byte数据用来存储一共有多少个restart结构数据。
图1 Block 承载的数据流
restart数据
每个restart数据可以保存完整的key,然后是该key对应的完整的value数据。再后续的数据其中的key都是和上一个数据的差值部分,value数据依然是完整存储。
差值key的数据表示:
*shared = reinterpret_cast<const uint8_t*>(p)[0];
*non_shared = reinterpret_cast<const uint8_t*>(p)[1];
value表示:
*value_length = reinterpret_cast<const uint8_t*>(p)[2];
经过若干个这样的结构,又会重新有一个restart结构。
整个entry的内存组织形式如图2所示
图2 单个entry的内存数据组织
restart数据检索
restart结构offset结构数据数组的起始地址:
restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t);
restart_offset_之后,记录restart数据长度的最后4个byte之前,这段连续的数组里面就记录每个restart在整个数据里面的offset,具体的代码如下:
uint32_t GetRestartPoint(uint32_t index) {assert(index < num_restarts_);return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t));}
里面的restarts_是restart_offset_,他们俩是同一个数据。
Block Iterator
Block Iterator 是 Block 和 外界的一个interface,对Block的操作都是通过对Iterator的函数方法使用来实现的。下面介绍一下这些函数方法。
Iterator的数据成员
//comparator 是比较key的函数方法
const Comparator* const comparator_;//对应Block里面的data指针
const char* const data_;//restart的数量
uint32_t const num_restarts_;//iterator当前位置的offset
uint32_t current_;//iterator当前所对应的restart的index
uint32_t restart_index_;//当前位置entry的key序列
std::string key_;//当前位置entry的value序列
Slice value_;Status status_;
Iterator的函数成员
//key 比较
inline int Compare(const Slice& a, const Slice& b) //下一个entry(key-value结构存储)的地址(offset)。
inline uint32_t NextEntryOffset() const //该函数提供第index个restart的地址(offset)
uint32_t GetRestartPoint(uint32_t index)//当前Iterator是否合法,这里面主要是通过当前entry的offset是否已经超过了数据所在的地址范围
bool Valid() const//获得下一个entry的key和value,同时更新current_和restart_index_
bool ParseNextKey()//整个Iterator位置调整到第一个restart位置,以及更新key_、value_、current_和restart_index_到第一个entry值
void SeekToFirst() override//整个Iterator位置调整到最后一个restart位置,以及更新key_、value_、current_和restart_index_到最后一个entry值
void SeekToLast() override//检索特定的key,在restart检索用到了二分查找,找到特定的restart使用线性查找
void Seek(const Slice& target) override//整个Iterator位置调整到当前位置的前一个位置,以及更新key_、value_、current_和restart_index_到当前位置前一个位置的entry值
void Prev() override//整个Iterator位置调整到当前位置的前一个位置,以及更新key_、value_、current_和restart_index_到当前位置后一个位置的entry值
void Next() override
总结
总结一下,Block是一个核心结构,代码逻辑清晰。这里面有几点好的设计可以借鉴:
1.使用restart + diff key的形式,由于key都是有序的,所以一个key和他前一个key的序列重复度应该是很高的,这样做可以有效提高存储效率。
2.使用restart,有益于检索效率,可以应用二分检索,否则只能使用顺序检索。
3.Block Iterator Iterator的使用恰当好处。