什么是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 的方式。
从描述上看,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位。
原文链接:https://blog.csdn.net/huntinux/article/details/51690665