LevelDB 源码阅读之 Slice

slice.h 的位置

Slice 被定义在 leveldb 的 include 目录里,目录树如下:

$tree include/
include/
└── leveldb
    ├── cache.h
    ├── c.h
    ├── comparator.h
    ├── db.h
    ├── dumpfile.h
    ├── env.h
    ├── filter_policy.h
    ├── iterator.h
    ├── options.h
    ├── slice.h
    ├── status.h
    ├── table_builder.h
    ├── table.h
    └── write_batch.h

Slice 的成员变量

Slice 只有两个成员变量,且都是私有的:

private:
  const char* data_;
  size_t size_;

需要注意的是,data_ 仅仅是一个 cha* 的指针,用来指向存储数据地址,同时使用了一个 size_ 来表示这个数据的长度。由此可以猜想:由 slice 表示的数据中间可以出现 '\0',也就是说,它可以用来表示非 C 字符串,但是还需要注意的是,我们不要随意的使用局部变量,尤其是栈里的局部变量来初始化一个 Slice,因为随着栈空间的回收,Slice 里 data_ 指向的数据也就回收了,这时候再使用 Slice 里的数据的时候肯定会产生 core dump,正如整个头文件中作者说明的那样:

The user of a Slice must ensure that the slice is not used after the corresponding external storage has been deallocated.

slice 的成员函数

slice 的所有成员函数都是公有的,对外提供了一系列的 API 来操作 slice:

构造函数

slice 定义了 四个构造函数,分别是:

Slice() : data_(""), size_(0) { }
Slice(const char* d, size_t n) : data_(d), size_(n) { }
Slice(const std::string& s) : data_(s.data()), size_(s.size()) { }
Slice(const char* s) : data_(s), size_(strlen(s)) { }

第一个是默认构造函数,它会初始化一个空的 slice 出来,这个 slice 的长度为 0
第二个构造函数要求提供一个 char* 和 size_t 的参数分别来初始化 slice 的两个成员变量
第三个构造函数的参数是一个 string 类型的字符串
第四个构造函数的参数是一个 C 语言类型的字符串,该字符串以 '\0' 结尾,并且用 strlen(s) 来初始化 slice 的长度

重载运算符

slice 对 '[]'、'==' 和 '!=' 进行了重载

char operator[](size_t n) const {
  assert(n < size());
  return data_[n];
}

对 '[]' 的重载,使得可以像 slice[i] 这样的返回第 i 个 char,不过需要注意的是:这里返回的只是一个 char 的拷贝而不是引用,因此,如果你想通过 slice[i] = 'a' 的方式来改变 slice 第 i 个字节的内容是会失败的

inline bool operator==(const Slice& x, const Slice& y) {
  return ((x.size() == y.size()) &&
          (memcmp(x.data(), y.data(), x.size()) == 0));
}

对于 '==',如果两个 slice 相等指的是是他们的长度相等,并且所有的字节相等。注意这里的 if 短路求值,先判断长度再判断 buffer 里面的内容,这里也是性能优化的一个小 trick 吧

inline bool operator!=(const Slice& x, const Slice& y) {
  return !(x == y);
}

对于 '!=' 就直接是调用的 '==' 了。没什么好解释的

常规函数

const char* data() const { return data_; }
size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
void clear() { data_ = ""; size_ = 0; }
void remove_prefix(size_t n)
std::string ToString() const { return std::string(data_, size_); }
int compare(const Slice& b)
bool starts_with(const Slice& x)

这两个函数:data() 和 size() 类似于 string 的 data() 和 size(),直接返回成员变量

empty() 是用来判断 buffer 中是否有数据的,这是通过判断 size 是否为 0 来实现的。值得一提的是 clear() 函数,它并没有释放以前使用的 buffer,而是直接让当前这个 slice 指向一个空的 buffer,并设置 size 为 0,这就意味着用户在调用 clear 之前需要手动的清空 slice 曾经使用的内存,避免造成内存泄露

ToString() 就直接调用 string 的构造函数生成一个 string 对象,并把这个对象拷贝给调用者。这里没有使用引用,因为是一个局部变量。如果返回的是一个指针的话,倒是可以 new 一个 string 并把指针返回回去,这样的话就可以避免一次内存拷贝了。

我们单独看看 remove_prefix() 函数:

void remove_prefix(size_t n) {
  assert(n <= size());
  data_ += n;
  size_ -= n;
}

它的实现方式也非常的简单:把当前指针前移 n 个字节,并且把 size 减 n。同样的,在使用这个函数的时候也需要手动的进行内存管理:避免内存泄露

inline int Slice::compare(const Slice& b) const {
  const size_t min_len = (size_ < b.size_) ? size_ : b.size_;
  int r = memcmp(data_, b.data_, min_len);
  if (r == 0) {
    if (size_ < b.size_) r = -1;
    else if (size_ > b.size_) r = +1;
  }
  return r;
}

这是 slice 自己定义的 compare 函数,这也很像 C 字符串的比较函数,逐字节的比较,相等返回 0,小于返回 -1,大于返回 +1

bool starts_with(const Slice& x) const {
  return ((size_ >= x.size_) &&
          (memcmp(data_, x.data_, x.size_) == 0));
}

两个 Slice a 和 b,a.start_with(b) 返回的就是 a 的前面部分是否是和 b 相等的。注意这里的短路:先判断 a 的 size 是否大于 b 的 size

你可能感兴趣的:(LevelDB 源码阅读之 Slice)