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