本文記錄一下使用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_指針指向的内容隻能是常量區域的一個固定的位址,并不會為各位新開辟一個存儲空間。