LevelDB : Varint

参考:
1. http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
2. http://blog.csdn.net/sparkliang/article/details/8573618

参考文章1是讲解Protobuf的,文中也介绍了Varint,这里做个摘录:

什么是Varint

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。下面就详细介绍一下 Varint。

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010

下图演示了 Google Protocol Buffer 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用 little-endian 的方式。

LevelDB : Varint_第1张图片

从描述上看,Varint与Utf-8编码有些类似,是变长编码。

Varint的实现分析

看懂上面的例子应该不难,下面来看看LevelDB中是如何实现的,位置是util\coding.cc

// 计算编码后的长度是几个字节
int VarintLength(uint64_t v) {
  int len = 1;
  while (v >= 128) {
    v >>= 7;
    len++;
  }
  return len;
}

// 将一个32位无符号int进行编码保存到dst指向的空间
char* EncodeVarint32(char* dst, uint32_t v) {
  // Operate on characters as unsigneds
  unsigned char* ptr = reinterpret_cast<unsigned char*>(dst);
  static const int B = 128;
  if (v < (1<<7)) {
    *(ptr++) = v; 
  } else if (v < (1<<14)) {
    *(ptr++) = v | B; 
    *(ptr++) = v>>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<char*>(ptr);
}

没看源码时自己写的代码是一个循环,看了源码才发现自己还是too young too simple, naive

分析这个分支发生了什么:

if (v < (1<<14)) {
    *(ptr++) = v | B; 
    *(ptr++) = v>>7; 
}

其中v是uint32_t, B是int 值为128, ptr是一个指向unsiged char类型的指针变量。
if是判断v是否需要两个字节表示。如果是就进入该分支进行处理。
首先要清楚,如果一个表达式中既有无符号数又有int时(有符号数), 那么int值将会转换为无符号数。(见C++ Primer 第五版 P34)
那么对于v | B, B将转换成uint32_t, 那么 v | B 就等价于:
v | 00000000 00000000 00000000 10000000
结果记为m , m就是将v的第8位置1后的值。(因为肯定会有后续字节,所以第八位肯定是1.)
然后计算*(ptr++) = v | B, 即 *(ptr++) = m
由于*ptr为unsigned char类型,而m为uint32_t, 这时会发生截断。*ptr即为m的最后8位。

其他参考
关于reinterpret_cast, dynamic_cast,const_cast, static_cast: http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html
需要指出的是,dynamic_cast对于指针转换失败时将指针设置为NULL,但是对于引用则会抛出bad_cast异常。

你可能感兴趣的:(LevelDB)