最近在尝试看leveldb的源码,这个是好久之前就选定的作为学习别人代码的一个项目,只是因为各种懒惰,才不得不一次一次的开始和丢下,为了能又一个学习的总结和继续,我打算不定时的做一下愿下的注释和分析。下边这篇文章作为第一篇。
项目源码
void PutFixed32(std::string* dst, uint32_t value); void PutFixed64(std::string* dst, uint64_t value); void PutVarint32(std::string* dst, uint32_t value); void PutVarint64(std::string* dst, uint64_t value); void PutLengthPrefixedSlice(std::string* dst, const Slice& value);
bool GetVarint32(Slice* input, uint32_t* value);
bool GetVarint64(Slice* input, uint64_t* value);
bool GetLengthPrefixedSlice(Slice* input, Slice* result);
const char* GetVarint32Ptr(const char* p, const char* limit, uint32_t* v);
const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* v);
void EncodeFixed32(char* buf, uint32_t value) {
if (port::kLittleEndian) { // 如果平台是小头在后的类型,直接调用memcpy函数按照字节拷贝即可。
memcpy(buf, &value, sizeof(value));
} else { /*如果是大头在后,需要逐个字节拷贝实现翻转。*/
buf[0] = value & 0xff;//存储最低8位
buf[1] = (value >> 8) & 0xff;//存储value的倒数第二个字节
buf[2] = (value >> 16) & 0xff;//存储value的倒数第三个字节
buf[3] = (value >> 24) & 0xff;//存储32位数据的最高字节。
}
}
void EncodeFixed64(char* buf, uint64_t value) {
if (port::kLittleEndian) {
memcpy(buf, &value, sizeof(value));
} else {
buf[0] = value & 0xff;
buf[1] = (value >> 8) & 0xff;
buf[2] = (value >> 16) & 0xff;
buf[3] = (value >> 24) & 0xff;
buf[4] = (value >> 32) & 0xff;
buf[5] = (value >> 40) & 0xff;
buf[6] = (value >> 48) & 0xff;
buf[7] = (value >> 56) & 0xff;
}
}
这里有一个疑问,只有编码还没有看到对应的解码函数,我会在后续的源码阅读中关注。
void PutFixed32(std::string* dst, uint32_t value) {
char buf[sizeof(value)];//首先开辟空间,注意没有考虑C类型的尾符'\0'
EncodeFixed32(buf, value);//直接调用上边的固定32位编码
dst->append(buf, sizeof(buf));//直接将编码的数据放到字符串
}
又一个疑问,为什么不直接使用boots::lexical_cast函数做这种转换的,我只能是认为提高性能减少空间使用。
void PutFixed64(std::string* dst, uint64_t value) {
char buf[sizeof(value)];
EncodeFixed64(buf, value);
dst->append(buf, sizeof(buf));
}
为了节省存储空间的考虑,存在很多整形数字很小,不足以占用32位,为了省略空间的考虑使用变长存储的方式。
这是编码函数中最难理解的函数。我在函数中发现移位7位,用7做单位,刚开始有点疑问为什么不是8位,于是,还在GitHub上提交了一个issue,后来接着看发现了原因。这个每个字节的最高位又个特殊用途,作为一个在高直接中是否还有数据的一个标志位。
char* EncodeVarint32(char* dst, uint32_t v) {
// Operate on characters as unsigneds
unsigned char* ptr = reinterpret_cast(dst);//重新解释下边存储无符号数据
static const int B = 128;
if (v < (1<<7)) {//如果要存储的数据小于1<<7(128),即0b1000000
*(ptr++) = v;//直接存储
} else if (v < (1<<14)) {// 如果数据小于1<<14(16384)即0b100000000000000
*(ptr++) = v | B;//ptr是char类型,截断v的最低7位,并且最高位也就是第8位置位位1,表示还有数据需要存储。
*(ptr++) = v>>7;//存储剩下的位数(不超过7位)
} else if (v < (1<<21)) {//和上诉类似
*(ptr++) = v | B;
*(ptr++) = (v>>7) | B;
*(ptr++) = v>>14;
} else if (v < (1<<28)) {
*(ptr++) = v | B;
*(ptr++) = (v>>7) | B;
*(ptr++) = (v>>14) | B;
*(ptr++) = v>>21;
} else {
*(ptr++) = v | B;
*(ptr++) = (v>>7) | B;
*(ptr++) = (v>>14) | B;
*(ptr++) = (v>>21) | B;
*(ptr++) = v>>28;
}
return reinterpret_cast(ptr);//指向最后一个字节的后边。
}
实际分析一下这个函数:
使用如下的代码片段:
std::string test;
uint32_t value1;
PutVarint32(&test, 3556);
std::cout << test << std::endl;
GetVarint32(new Slice(test), &value1);
std::cout << value1 << std::endl;
并且替换和上诉代码片段
} else if (v < (1<<14)) {
std::cout << (1<<14) << std::endl;
*(ptr++) = v | B;
std::bitset<8> x(*(ptr - 1));
std::cout << x << std::endl;
printf("%c, %d.\n", *(ptr-1), *(ptr-1));
*(ptr++) = v>>7;
std::bitset<8> y(*(ptr - 1));
std::cout << y << std::endl;
printf("%c, %d.\n", *(ptr-1), *(ptr-1));
首先我们尝试存储3556。
3556 < (1<<14)
3556 = 0b110111100100
0b110111100100 | 128 = 0b110111100100(不变),注意第8位是1,表示1个字节的空间不够。
然后将低8个字节存储到ptr所指向的字节空间,也就是低8位 0b11100100
然后将0b110111100100整体右移7位,相当于地字节的第8位数字变成了最低位0b11011。然后存储在ptr现在指向的字节。
也就是字符串存储了ascii对应的,先是0b11100100(228),然后0b11011(27)
上诉代码片段的输出是:
16384
11100100
\344, 228.
00011011
, 27.
3556
和我们的分析是一致的。
void PutVarint32(std::string* dst, uint32_t v) {
char buf[5];
char* ptr = EncodeVarint32(buf, v);
dst->append(buf, ptr - buf);
}
上诉函数直接调用了上边分析过的函数。
char* EncodeVarint64(char* dst, uint64_t v) {
static const int B = 128;
unsigned char* ptr = reinterpret_cast(dst);
while (v >= B) {
*(ptr++) = (v & (B-1)) | B;
v >>= 7;
}
*(ptr++) = static_cast(v);
return reinterpret_cast(ptr);
}
只是使用了一个while循环,而不是一个一个展开。
void PutVarint64(std::string* dst, uint64_t v) {
char buf[10];
char* ptr = EncodeVarint64(buf, v);
dst->append(buf, ptr - buf);
}
直接使用了上诉函数,不做具体分析。