天天看点

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_指针指向的内容只能是常量区域的一个固定的地址,并不会为各位新开辟一个存储空间。

继续阅读