leveldb中coding源文件的小分析

leveldb中coding源文件的小分析


最近在尝试看leveldb的源码,这个是好久之前就选定的作为学习别人代码的一个项目,只是因为各种懒惰,才不得不一次一次的开始和丢下,为了能又一个学习的总结和继续,我打算不定时的做一下愿下的注释和分析。下边这篇文章作为第一篇。

项目源码

1 代码首先包括两类函数

1.1 从数据的存储和获取来分类是如下:

1.1.1 put系列

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

1.1.2 get系列

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

1.2 从数据类型上来讲是如下:

1.2.1 一种32位类型——uint32_t
1.2.2 一种64位类型——uint64_

2 函数具体分析:

2.1 编码固定位数函数

2.1.1 编码固定32位类型,代码里面会有平台相关内容,比如大小头
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位数据的最高字节。
  }
}
2.1.2 编码固定64位类型,和上诉1中的类似,在此不再赘述。
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;
  }
}

这里有一个疑问,只有编码还没有看到对应的解码函数,我会在后续的源码阅读中关注。

2.2 存储固定位数函数:

2.2.1 存储固定32位整形
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函数做这种转换的,我只能是认为提高性能减少空间使用。

2.2.2 存储固定64位整形,和上诉相同,不再赘述。
void PutFixed64(std::string* dst, uint64_t value) {
  char buf[sizeof(value)];
  EncodeFixed64(buf, value);
  dst->append(buf, sizeof(buf));
}

2.3 存储变长位数的函数

为了节省存储空间的考虑,存在很多整形数字很小,不足以占用32位,为了省略空间的考虑使用变长存储的方式。

2.3.1 存储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);
}

上诉函数直接调用了上边分析过的函数。

2.3.2 存储64位变长数据,和上边类似。
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);
}

直接使用了上诉函数,不做具体分析。

你可能感兴趣的:(c/c++源代码,leveldb)