天天看點

Rocksdb Slice使用中的一個小坑

本文記錄一下使用Rocksdb Slice過程中的一個小小坑,差點沒一口老血吐出來。

rocksdb的Slice 資料結構是一個小型得不可變類string資料結構,設計出來的目的是為了保證rocksdb内部處理使用者輸入的key在從記憶體到持久化磁盤的整個處理鍊路是不會被修改的,比較友善得從​

​user_key​

​​ 解碼出​

​internal_key​

​。

如果把這個資料結構當作普通字元串來使用,會有意想不到的 收獲。

​Slice​

​資料結構代碼如下:

class Slice {
 public:
  // Create an empty slice.
  Slice() : data_(""), size_(0) {}

  // Create a slice that refers to d[0,n-1].
  Slice(const char* d, size_t n) : data_(d), size_(n) {}

  // Create a slice that refers to the contents of "s"
  /* implicit */
  Slice(const std::string& s) : data_(s.data()), size_(s.size()) {}

#ifdef __cpp_lib_string_view
  // Create a slice that refers to the same contents as "sv"
  /* implicit */
  Slice(std::string_view sv) : data_(sv.data()), size_(sv.size()) {}
#endif

  // Create a slice that refers to s[0,strlen(s)-1]
  /* implicit */
  Slice(const char* s) : data_(s) {
    cout << "called Slice(const char* s)" << endl;
    size_ = (s == nullptr) ? 0 : strlen(s); }

  // Create a single slice from SliceParts using buf as storage.
  // buf must exist as long as the returned Slice exists.
  Slice(const struct SliceParts& parts, std::string* buf);

  // Return a pointer to the beginning of the referenced data
  const char* data() const { return data_; }

  // Return the length (in bytes) of the referenced data
  size_t size() const { return size_; }

  // Return true iff the length of the referenced data is zero
  bool empty() const { return size_ == 0; }

  // Return the ith byte in the referenced data.
  // REQUIRES: n < size()
  char operator[](size_t n) const {
    assert(n < size());
    return data_[n];
  }
  ...

  // Return a string that contains the copy of the referenced data.
  // when hex is true, returns a string of twice the length hex encoded (0-9A-F)
  // Return a string that contains the copy of the referenced data.
  std::string ToString(bool hex = false) const {
    std::string result;  // RVO/NRVO/move
    if (hex) {
      result.reserve(2 * size_);
      for (size_t i = 0; i < size_; ++i) {
        unsigned char c = data_[i];
        result.push_back(toHex(c >> 4));
        result.push_back(toHex(c & 0xf));
      }
      return result;
    } else {
      result.assign(data_, size_);
      return result;
    }
  } 
  ......

  // Compare two slices and returns the first byte where they differ
  size_t difference_offset(const Slice& b) const;

  // private: make these public for rocksdbjni access
  const char* data_;
  size_t size_;

  // Intentionally copyable
};      

主要是其維護了一個​

​const char* data_​

​​成員變量,當我們使用者通過​

​db->Put(opts, "key","value",)​

​​時會調用​

​Slice(const std::string& s)​

​構造函數,并不會維護一個新的存儲空間,而是使用本來的預設構造函數為data_配置設定的位址空間。

坑就來了,也就是想要通過如下方式建立一個新的Slice變量:

map<Slice,int, MetaCmp> mp;
void AddSlice(const Slice& buf, const int& value) {
  if(mp.find(buf) != mp.end()) {
    mp[buf] = value;
  } else {
    mp.insert(pair<Slice, int>(buf, value));
  }
}

int main() {
  for(int i = 0;i < 10; i++) {
    Slice str("meta" + to_string(i));
    AddSlice(str, i);
  }
  return 0;
}      

會發現每次建立的Slice都會放在一個位址中,你以為你建立了9個Slice,并且存放到了一個map中,但實際上Slice的資料位址其實隻有一個内容。

called default constructor 0x10e756290
called Slice(const std::string& s) meta0 0x7ffee14b5738
called Slice(const std::string& s) meta1 0x7ffee14b5738
called Slice(const std::string& s) meta2 0x7ffee14b5738
called Slice(const std::string& s) meta3 0x7ffee14b5738
called Slice(const std::string& s) meta4 0x7ffee14b5738
called Slice(const std::string& s) meta5 0x7ffee14b5738
called Slice(const std::string& s) meta6 0x7ffee14b5738
called Slice(const std::string& s) meta7 0x7ffee14b5738
called Slice(const std::string& s) meta8 0x7ffee14b5738
called Slice(const std::string& s) meta9 0x7ffee14b5738
key: meta9 value: 9      

也就是當我們調用​

​Slice str("meta" + to_string(i));​

​​構造一個str對象時,實際存放資料的記憶體空間内容就已經被修改了,後續的​

​AddSlice​

​函數已經其内部添加到一個map中的操作其實都是在針對已存在的key進行的操作。

歸根結底就是其維護了一個​

​const char* data_;​

​成員變量,它限制了當我們啟動一個程序時data_指針指向的内容隻能是常量區域的一個固定的位址,并不會為各位新開辟一個存儲空間。

繼續閱讀