zoukankan      html  css  js  c++  java
  • levelDB Block

    http://blog.csdn.net/sparkliang/article/details/8635821

     BlockBuilder的接口

    首先从Block的构建开始,这就是BlockBuilder类,来看下BlockBuilder的函数接口,一共有5个:

    [cpp] view plaincopy
     
    1. void Reset(); // 重设内容,通常在Finish之后调用已构建新的block  
    2. //添加k/v,要求:Reset()之后没有调用过Finish();Key > 任何已加入的key  
    3. void Add(const Slice& key,const Slice& value);  
    4. // 结束构建block,并返回指向block内容的指针  
    5. Slice Finish();// 返回Slice的生存周期:Builder的生存周期,or直到Reset()被调用  
    6. size_t CurrentSizeEstimate()const; // 返回正在构建block的未压缩大小—估计值  
    7. bool empty() const { returnbuffer_.empty();} // 没有entry则返回true  

    主要成员变量如下:

    [cpp] view plaincopy
     
    1. std::string            buffer_; // block的内容  
    2. std::vector<uint32_t>  restarts_;  // 重启点-后面会分析到  
    3. int                  counter_;  // 重启后生成的entry数  
    4. std::string            last_key_; // 记录最后添加的key  

    6.3.2 BlockBuilder::Add()

    调用Add函数向当前Block中新加入一个k/v对{key, value}。函数处理逻辑如下:

    S1 保证新加入的key > 已加入的任何一个key;

    [cpp] view plaincopy
     
    1. assert(!finished_);    
    2. assert(counter_ <= options_->block_restart_interval);  
    3. assert(buffer_.empty() || options_->comparator->Compare(key,last_key_piece) > 0);  

    S2 如果计数器counter < opions->block_restart_interval,则使用前缀算法压缩key,否则就把key作为一个重启点,无压缩存储;

    [cpp] view plaincopy
     
    1. Slice last_key_piece(last_key_);  
    2. if (counter_ < options_->block_restart_interval) { //前缀压缩  
    3.     // 计算key与last_key_的公共前缀  
    4.     const size_t min_length= std::min(last_key_piece.size(), key.size());  
    5.     while ((shared < min_length)&& (last_key_piece[shared] == key[shared])) {  
    6.     shared++;  
    7. }else{ // 新的重启点  
    8.     restarts_.push_back(buffer_.size());  
    9.     counter_ = 0;  
    10. }  

    S3根据上面的数据格式存储k/v对,追加到buffer中,并更新block状态。

    [cpp] view plaincopy
     
    1. const size_t non_shared = key.size() - shared; // key前缀之后的字符串长度  
    2. // append"<shared><non_shared><value_size>" 到buffer_    
    3. PutVarint32(&buffer_, shared);   
    4. PutVarint32(&buffer_, non_shared);   
    5. PutVarint32(&buffer_, value.size());    
    6. // 其后是前缀之后的字符串 + value   
    7. buffer_.append(key.data() + shared, non_shared);    
    8. buffer_.append(value.data(), value.size());    
    9. // 更新状态 ,last_key_ = key及计数器counter_  
    10. last_key_.resize(shared);   // 连一个string的赋值都要照顾到,使内存copy最小化  
    11. last_key_.append(key.data() + shared, non_shared);   
    12. assert(Slice(last_key_) == key);    
    13. counter_++;    

    6.3.3 BlockBuilder::Finish()

    调用该函数完成Block的构建,很简单,压入重启点信息,并返回buffer_,设置结束标记finished_:

    [cpp] view plaincopy
     
    1. for (size_t i = 0; i < restarts_.size(); i++) {  // 重启点    
    2.       PutFixed32(&buffer_, restarts_[i]);    
    3. }    
    4. PutFixed32(&buffer_, restarts_.size());    // 重启点数量    
    5. finished_ = true;    
    6. return Slice(buffer_);    

    6.3.4 BlockBuilder::Reset() & 大小

    还有Reset和CurrentSizeEstimate两个函数,Reset复位函数,清空各个信息;函数CurrentSizeEstimate返回block的预计大小,从函数实现来看,应该在调用Finish之前调用该函数。

    [cpp] view plaincopy
     
    1. void BlockBuilder::Reset() {    
    2.    buffer_.clear();  restarts_.clear();  last_key_.clear();    
    3.    restarts_.push_back(0);       // 第一个重启点位置总是 0    
    4.    counter_ = 0;    
    5.    finished_ = false;    
    6. }    
    7.   
    8. size_t BlockBuilder::CurrentSizeEstimate () const {    
    9.    // buffer大小 +重启点数组长度 + 重启点长度(uint32)  
    10.   return (buffer_.size() +  restarts_.size() * sizeof(uint32_t) + sizeof(uint32_t));   
    11. }    

    Block的构建就这些内容了,下面开始分析Block的读取,就是类Block。

    6.3.5 Block类接口

    对Block的读取是由类Block完成的,先来看看其函数接口和关键成员变量。

    Block只有两个函数接口,通过Iterator对象,调用者就可以遍历访问Block的存储的k/v对了;以及几个成员变量,如下:

    [cpp] view plaincopy
     
    1.   size_t size() const { returnsize_; }  
    2.   Iterator* NewIterator(constComparator* comparator);  
    3.   
    4.   const char* data_; // block数据指针  
    5.   size_t size_;      // block数据大小  
    6.   uint32_t restart_offset_;     // 重启点数组在data_中的偏移  
    7.   bool owned_;              //data_[]是否是Block拥有的  

    6.3.6 Block初始化

    Block的构造函数接受一个BlockContents对象contents初始化,BlockContents是一个有3个成员的结构体。

    [cpp] view plaincopy
     
    1.   >data = Slice();  
    2.   >cachable = false; // 无cache  
    3.   >heap_allocated = false; // 非heap分配  
    4. 根据contents为成员赋值  
    5. data_ = contents.data.data(), size_ =contents.data.size(),owned_ = contents.heap_allocated;  

    然后从data中解析出重启点数组,如果数据太小,或者重启点计算出错,就设置size_=0,表明该block data解析失败.

    [cpp] view plaincopy
     
    1. if (size_ < sizeof(uint32_t)){  
    2.   size_ = 0;  // 出错了  
    3. else {  
    4.   restart_offset_ = size_ - (1 +NumRestarts()) * sizeof(uint32_t);  
    5.   if (restart_offset_ > size_- sizeof(uint32_t)) size_ = 0;  
    6. }  

    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的实现。

    主要成员变量有:

    [cpp] view plaincopy
     
    1. const Comparator* constcomparator_; // key比较器  
    2. const char* const data_;      // block内容  
    3. uint32_t const restarts_;     // 重启点(uint32数组)在data中的偏移  
    4. uint32_t const num_restarts_; // 重启点个数  
    5. uint32_t current_; // 当前entry在data中的偏移.  >= restarts_表明非法  
    6. uint32_t restart_index_;  // current_所在的重启点的index  

    下面来看看对Iterator接口的实现,简单函数略过。

    >首先是Next()函数,直接调用private函数ParseNextKey()跳到下一个k/v对,函数实现如下:

    S1 跳到下一个entry,其位置紧邻在当前value_之后。如果已经是最后一个entry了,返回false,标记current_为invalid。

    [cpp] view plaincopy
     
    1. current_ = NextEntryOffset(); // (value_.data() + value_.size()) - data_  
    2. const char* p = data_ +current_;  
    3. const char* limit = data_ +restarts_; // Restarts come right after data  
    4. if (p >= limit) { // entry到头了,标记为invalid.  
    5.   current_ = restarts_;  
    6.   restart_index_ =num_restarts_;  
    7.   return false;  
    8. }  

    S2 解析出entry,解析出错则设置错误状态,记录错误并返回false。解析成功则根据信息组成key和value,并更新重启点index。

    [cpp] view plaincopy
     
    1. uint32_t shared, non_shared,value_length;  
    2. p = DecodeEntry(p, limit,&shared, &non_shared, &value_length);  
    3. if (p == NULL || key_.size()< shared) {  
    4.   CorruptionError();  
    5.   return false;  
    6. else { // 成功  
    7.   key_.resize(shared);  
    8.   key_.append(p, non_shared);  
    9.   value_ = Slice(p +non_shared, value_length);  
    10.   while (restart_index_ + 1< num_restarts_ && GetRestartPoint(restart_index_ + 1) < current_) {  
    11.        ++restart_index_; //更新重启点index  
    12.   }  
    13.   return true;  
    14. }  

    函数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对开始位置。

    [cpp] view plaincopy
     
    1. const uint32_t original =current_;  
    2. while (GetRestartPoint(restart_index_)>= original) {  
    3.         if (restart_index_ == 0) { // 到第一个entry了,标记invalid状态  
    4.             current_ = restarts_;  
    5.             restart_index_ =num_restarts_;  
    6.             return;  
    7.       }  
    8.       restart_index_--;  
    9. }  
    10. 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值。

    [cpp] view plaincopy
     
    1. void SeekToRestartPoint(uint32_tindex) {  
    2.   key_.clear();  
    3.   restart_index_ = index;  
    4.   // ParseNextKey()会设置current_;  
    5.   //ParseNextKey()从value_结尾开始, 因此需要相应的设置value_  
    6.   uint32_t offset =GetRestartPoint(index);  
    7.   value_ = Slice(data_ + offset,0); // value长度设置为0,字符串指针是data_+offset  
    8. }  

    > SeekToFirst/Last,这两个函数都很简单,借助于前面的SeekToResartPoint函数就可以完成。

    [cpp] view plaincopy
     
    1. virtual void SeekToFirst() {  
    2.   SeekToRestartPoint(0);  
    3.   ParseNextKey();  
    4. }  
    5.   
    6. virtual void SeekToLast() {  
    7.   SeekToRestartPoint(num_restarts_ - 1);  
    8.   while (ParseNextKey()&& NextEntryOffset() < restarts_) {} //Keep skipping  
    9. }  

    > 最后一个Seek函数,跳到指定的target(Slice),函数逻辑如下:

    S1 二分查找,找到key < target的最后一个重启点,典型的二分查找算法,代码就不再贴了。

    S2 找到后,跳转到重启点,其索引由left指定,这是前面二分查找到的结果。如前面所分析的,value_指向重启点的地址,而size_指定为0,这样ParseNextKey函数将会取出重启点的k/v值。

        SeekToRestartPoint(left);

    S3 自重启点线性向下,直到遇到key>= target的k/v对。

    [cpp] view plaincopy
     
    1. while (true) {  
    2.   if (!ParseNextKey()) return;  
    3.   if (Compare(key_, target)>= 0) return;  
    4. }  

    上面就是Block::Iter的全部实现逻辑,这样Block的创建和读取遍历都已经分析完毕。

  • 相关阅读:
    javascript线性渐变2
    javascript无缝滚动2
    javascript Object对象
    javascript无缝滚动
    javascript图片轮换2
    javascript图片轮换
    用C/C++写CGI程序
    linux shell 的 for 循环
    重磅分享:微软等数据结构+算法面试100题全部答案完整亮相
    查看linux服务器硬盘IO读写负载
  • 原文地址:https://www.cnblogs.com/shenzhaohai1989/p/3910726.html
Copyright © 2011-2022 走看看