levelDB Block

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

 BlockBuilder的接口

首先從Block的構建開始,這就是BlockBuilder類,來看下BlockBuilder的函數接口,一共有5個:算法

[cpp]  view plain copy
 
  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 plain copy
 
  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}。函數處理邏輯以下:app

S1 保證新加入的key > 已加入的任何一個key;函數

[cpp]  view plain copy
 
  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做爲一個重啓點,無壓縮存儲;ui

[cpp]  view plain copy
 
  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狀態。spa

 

[cpp]  view plain copy
 
  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_:.net

[cpp]  view plain copy
 
  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 plain copy
 
  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。rest

6.3.5 Block類接口

對Block的讀取是由類Block完成的,先來看看其函數接口和關鍵成員變量。code

Block只有兩個函數接口,經過Iterator對象,調用者就能夠遍歷訪問Block的存儲的k/v對了;以及幾個成員變量,以下:

[cpp]  view plain copy
 
  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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  1. while (true) {  
  2.   if (!ParseNextKey()) return;  
  3.   if (Compare(key_, target)>= 0) return;  
  4. }  

上面就是Block::Iter的所有實現邏輯,這樣Block的建立和讀取遍歷都已經分析完畢。

相關文章
相關標籤/搜索