leveldb源码学习1-slice

背景

长那么大还没看过开源项目源码,说出去太菜了,所以一直想找个C++开源项目学习一下。正好前阵子公司项目里用到了RocksDB,了解到它是leveldb的升级版,而且两个开源项目都是C++开发的,于是心动了。

本来想看RocksDB的,但看了下相比leveldb它升级了很多东西,源码多了很多,编译出来的静态库足有几百兆……所以退缩了,决定先看基础班leveldb,后面有机会再去了解RocksDB。

然而,看了几天leveldb……也好难啊……慢慢啃……

对于我这个没看过源码不知道阅读源码方法的本菜鸡来说,最好第一个阅读的项目能找到足够的解析文档来帮助学习。谷歌之后从网上找了阿里一位大佬写的《leveldb实现解析》,据说是分析leveldb比较好的文章,于是我也决定借助这篇文档来学习,希望也能学学如何看一个庞大的项目源码。

这是本次levedb系列学习的第一篇笔记,希望能坚持看完。

基本概念

Slice(include/leveldb/slice.h)

这其实就是leveldb中的string类。

leveldb没有使用std::string,不记得在哪看过,好像Google内部C++项目里都不使用标准库的string,而都是用slice类来代替,不知道是真是假。

slice.h其实就是字符串简单的封装,可以直接操控指针避免不必要的数据拷贝。

Note:感觉面试里让手写一个string类就可以参考这个,不过复制构造函数用的是default,尴尬了。

整体代码并不长,而且一目了然,陈列如下:

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
class LEVELDB_EXPORT 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"
Slice(const std::string& s) : data_(s.data()), size_(s.size()) {}

// Create a slice that refers to s[0,strlen(s)-1]
Slice(const char* s) : data_(s), size_(strlen(s)) {}

// Intentionally copyable.
Slice(const Slice&) = default;
Slice& operator=(const Slice&) = default;

// 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];
}

// Change this slice to refer to an empty array
void clear() {
data_ = "";
size_ = 0;
}

// Drop the first "n" bytes from this slice.
void remove_prefix(size_t n) {
assert(n <= size());
data_ += n;
size_ -= n;
}

// Return a string that contains the copy of the referenced data.
std::string ToString() const { return std::string(data_, size_); }

// Three-way comparison. Returns value:
// < 0 iff "*this" < "b",
// == 0 iff "*this" == "b",
// > 0 iff "*this" > "b"
int compare(const Slice& b) const;

// Return true iff "x" is a prefix of "*this"
bool starts_with(const Slice& x) const {
return ((size_ >= x.size_) && (memcmp(data_, x.data_, x.size_) == 0));
}

private:
const char* data_;
size_t size_;
};

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;
}

}

该文件本身没有什么值得说道的地方,唯一可以注意一下的是重载运算符!=方法的实现方法是调用重载运算符==,这种实现方式还是很多的,比如后缀++的实现中一般会调用前缀++的函数,然后解引用运算符*和下表运算符[]好像也用了类似的实现方法,不太记得了,可以看下stl实现里面。

接下来说一下该文件里类声明中包含的LEVELDB_EXPORT,该字段是一个宏,定义不在本文件中,而是在include/leveldb/export.h中,该文件内容如下:

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
#ifndef STORAGE_LEVELDB_INCLUDE_EXPORT_H_
#define STORAGE_LEVELDB_INCLUDE_EXPORT_H_

#if !defined(LEVELDB_EXPORT)

#if defined(LEVELDB_SHARED_LIBRARY)
#if defined(_WIN32)

#if defined(LEVELDB_COMPILE_LIBRARY)
#define LEVELDB_EXPORT __declspec(dllexport)
#else
#define LEVELDB_EXPORT __declspec(dllimport)
#endif // defined(LEVELDB_COMPILE_LIBRARY)

#else // defined(_WIN32)
#if defined(LEVELDB_COMPILE_LIBRARY)
#define LEVELDB_EXPORT __attribute__((visibility("default")))
#else
#define LEVELDB_EXPORT
#endif
#endif // defined(_WIN32)

#else // defined(LEVELDB_SHARED_LIBRARY)
#define LEVELDB_EXPORT
#endif

#endif // !defined(LEVELDB_EXPORT)

#endif // STORAGE_LEVELDB_INCLUDE_EXPORT_H_

说实话看到这玩意我是真的晕了,虽然大致能明白意思,但看的还是很难受,宏定义真的又强大又麻烦。到这里又不得不说谷歌C++规范里提到的在#endif或者右括号}后面跟上对应的注释实在是太必要了,比如上面代码里就在#else#endif后面添加了必要的注释,提高可读性(同样是不记得在哪里见过,预处理宏定义命令都建议顶格写,可能是因为这个原因所以这里没有缩进)。

其实还是第一次见到类似#if defined的写法,以前最多见过#ifdef,于是查了下资料,发现其实这两者很相似,唯一区别在于#ifdef#if defined的缩写形式,前者只能判断单个宏是否定义,而后者可以组成更加复杂的预编译条件。

比如#if defined(DEBUG) && VERSION>3,而用#ifdef必须这么写:

1
2
3
4
5
#ifdef DEBUG
#if VERSION>3
……
#endif
#endif

好了,解决了一些基本问题,可以来看最重要的有关LEVELDB_EXPORT的那几行。

这里涉及到几个之前没有接触过的函数,包括__declspec(dllexport)__declspec(dllimport)以及__attribute__((visibility("default"))),接下来一一进行简单解释。

函数 用处
__declspec(dllexport) 将一个函数声明为导出函数,可以省掉在DEF文件中手工定义导出哪些函数。当然如果DLL里都是C++的类的话,就只能导出类。
__declspec(dllimport) 相反,就是将一个函数声明为导入函数,是从其他动态库里引入的函数。一般情况下不使用该函数也能正确编译,但使用该函数可以使编译器生成更好的代码。不过当类中有静态变量的时候,必须要引入该函数,否则会报错。
attribute((visibility(“default”))) 控制共享文件导出符号,default表示用它修饰的符号将被导出,动态库中的函数默认是可见的。hidden则意味不可见。其中gcc的visibility是说,如果编译的时候用了这个属性,那么动态库的符号都是hidden的,除非强制声明。

综上,主要是为了一些导出符号考虑,暂时先了解一下,感觉就是为了让其他模块能够使用slice类吧,深层次的东西咱也不了解,知道就行。