0%

levelDB源码剖析(5)--字符串Slice

levelDB的字符串Slice


上次我们分析编码解码的时候,注意到其中使用的参数有些是Slice类型,这个类型就是levelDB中自己封装的一个轻量级字符串类,其只包含了指向字符串的指针和字符串的长度。

Slice可以理解成切片,因为其指向底层字符串的首地址,并且标定出长度。

但是需要注意的是,Slice中不涉及对字符串的销毁和创建,它只是指向。因此调用Slice对象时必须确保底层的字符串没有被销毁

源码解析部分

源码位置: utils/slice.h

没错,Slice对象只有一个头文件,全部方法都是内联的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

class LEVELDB_EXPORT Slice
{

private: //只有两个私有成员
const char *data_; // 指向位于底层的字符串
size_t size_; // 标识出字符串的长度

public:
/****start一些构造函数start****/

// 空的构造函数
Slice() : data_(""), size_(0) {}
// 对字符串d进行0:n的切片,即d[:n]
Slice(const char *d, size_t n) : data_(d), size_(n) {}
// 对string类型s进行0:n的切片,即s[:n]
Slice(const std::string &s) : data_(s.data()), size_(s.size()) {}
// 指向字符串s
Slice(const char *s) : data_(s), size_(strlen(s)) {}

// 拷贝构造函数和'='运算符使用的默认的即可
Slice(const Slice &) = default;
Slice &operator=(const Slice &) = default;

/****end一些构造函数end****/

/*下面是一些公有接口*/

// 指向切片字符串的起始位置
const char *data() const { return data_; }
// 返回长度
size_t size() const { return size_; }
// 判断是否为空
bool empty() const { return size_ == 0; }
// 执行类似字符串的str[i]的访问
char operator[](size_t n) const
{
assert(n < size());
return data_[n];
}

// 清除切片
void clear()
{
data_ = "";
size_ = 0;
}

// 移除切片前n个字节
void remove_prefix(size_t n)
{
assert(n <= size());
data_ += n;
size_ -= n;
}

// 将切片创建成string类
std::string ToString() const { return std::string(data_, size_); }

// 与另一个切片比较
int compare(const Slice &b) const;

// 此切片是否以切片x开头
bool starts_with(const Slice &x) const
{
return ((size_ >= x.size_) && (memcmp(data_, x.data_, x.size_) == 0));
}
};

// 判断x与y是否相等
inline bool operator==(const Slice &x, const Slice &y)
{
return ((x.size() == y.size()) &&
(memcmp(x.data(), y.data(), x.size()) == 0));
}

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

// 比较字符串的函数
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的实现还是非常简单的,而且它提供了非常丰富的接口。

但是Slice的实现我们也能看到一些问题:

  • 由于Slice类不涉及内存的分配和销毁,也不检查内存是否合法,因此在调用的时候需要特别注意。
  • Slice类不涉及原子操作,虽然大部分都是const的读取,但是在多线程中将某一个Slice对象进行修改,则所有线程都需要执行同步。

关于第二点,源文件中也有说明

Multiple threads can invoke const methods on a Slice without external synchronization, but if any of the threads may call a non-const method, all threads accessing the same Slice must use external synchronization.

多线程在调用const方法后可以不同步,但是如果调用非const方法,所有访问被修改的Slice的线程必须执行同步。

总结部分

编程小技巧

  • C++可以在类内或类外重载运算符,而在类内重载又可以分为'类内隐式重载'和在'类外显示重载',这几种方式区别如下。当然,此处不考虑友元。
1
2
3
4
5
6
7
8
9
10
11
12

class x
{
public:
...
int operator==(x& other){}; // 类内隐式
}

int inline x::operator==(x& other){};// 类外显示

int inline operator==(x& this, x& other){};//类外重载