在阅读Memtable
处理流程时看到了Varint32、Varint64
,一开始不是太理解,导致阅读Memtable
出现一定障碍,所以这里就单独列出来讲下,下文用Varint
特指Varint32
。
一、什么是变长编码
【定义】
Varint
是一种通过一个或多个字节来表示整数的方法。即之前用固定字节数来表示一个正数,Varint
通过可变的字节数来表示一个整数。
【范围】
对于32位(Varint32
)整形数经变长编码后占用1~5个Byte,小的数字用1个字节,大的数字用5个字节。
对于64为(Varint64
)整形数经变长编码后占用1~10个Byte,小的数字用1个字节,大的数字用10个字节。
【表示】
最高有效状态位msb:
- 为1则表明后面的字节还是属于当前数据的;
- 为0则表是当前字节是最后一个字节数据。
有效数据位:
每个变长编码字节用低7位来存储数字的二进制补码表示。
【举例】
leveldb中是以小端方式存储变长编码的,Varint
编码是取原数据二进制的每7位为一个单元。
图中Varint编码
最左边是低地址,最右边是高地址。
1:
因为127一个字节表示即可,后面已无字节是当前数据的,所以msb是0。
2:
leveldb的Varint编码是小端存储(即低字节存放在低地址处),同时128是8位二进制数,
所以Varint
编码需要两个字节。因为最右边字节是属于最左边,所以最左边编码msb位是1,
而最右边已是最后一个字节,后面已无字节,所以msb位是0。
3:
解释参考1、2。
二、作用
通过上面的例子我们看到127需要一个字节表示,而123456需要三个字节表示,如果固定用一个字节表示整数则最大是255(无符类型),无法表示123456这种超过255的。
如果用三个字节表示,对应小于255的这种只需一个字节表示的整数则浪费了两个字节。leveldb存储为了节省空间,则用一种变长编码的方式Varint
来表示整形。因为小的数字只占用1个字节,大的则会占用5个字节,而根据统计学来说,小数字使用频率大于大数字,所以利用Varint
编码能够起到压缩空间的作用。
三、leveldb中的实现
【编码】
<!编码参照上述对Varint的说明看起来应该很好懂>
char* EncodeVarint32(char* dst, uint32_t v) {
// Operate on characters as unsigneds
uint8_t* ptr = reinterpret_cast<uint8_t*>(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);
}
char* EncodeVarint64(char* dst, uint64_t v) {
static const int B = 128;
uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
while (v >= B) {
*(ptr++) = v | B;
v >>= 7;
}
*(ptr++) = static_cast<uint8_t>(v);
return reinterpret_cast<char*>(ptr);
}
【解码】
<!解码就是编码的逆过程,这里涉及到对Slice数据的解析
重点关注“GetVarint32Ptr()”,“GetVarint32PtrFallback()”
>
bool GetVarint32(Slice* input, uint32_t* value) {
const char* p = input->data();
const char* limit = p + input->size();
const char* q = GetVarint32Ptr(p, limit, value);
if (q == nullptr) {
return false;
} else {
*input = Slice(q, limit - q);
return true;
}
}
inline const char* GetVarint32Ptr(const char* p, const char* limit,
uint32_t* value) {
if (p < limit) {
uint32_t result = *(reinterpret_cast<const uint8_t*>(p));
if ((result & 128) == 0) {
<!128就是一个八位,最高位为1,其余位为0,
与128相与为0表示待解码的result<=127,所以长度就是一个字节
直接赋值给*Value即可。
>
*value = result;
return p + 1;
}
}
return GetVarint32PtrFallback(p, limit, value);
}
const char* GetVarint32PtrFallback(const char* p, const char* limit,
uint32_t* value) {
<!对于超过一个字节的长度编码,解码的过程就是按小端顺序,
每7位取出,然后移位来组装最后的实际长度,组装结束的表示就是MSB位为0>
uint32_t result = 0;
for (uint32_t shift = 0; shift <= 28 && p < limit; shift += 7) {
uint32_t byte = *(reinterpret_cast<const uint8_t*>(p));
p++;
if (byte & 128) {
// More bytes are present
result |= ((byte & 127) << shift);
} else {
result |= (byte << shift);
*value = result;
return reinterpret_cast<const char*>(p);
}
}
return nullptr;
}
<!Varint64位的流程可Varint32原理一样>
bool GetVarint64(Slice* input, uint64_t* value) {
const char* p = input->data();
const char* limit = p + input->size();
const char* q = GetVarint64Ptr(p, limit, value);
if (q == nullptr) {
return false;
} else {
*input = Slice(q, limit - q);
return true;
}
}
const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* value) {
uint64_t result = 0;
for (uint32_t shift = 0; shift <= 63 && p < limit; shift += 7) {
uint64_t byte = *(reinterpret_cast<const uint8_t*>(p));
p++;
if (byte & 128) {
// More bytes are present
result |= ((byte & 127) << shift);
} else {
result |= (byte << shift);
*value = result;
return reinterpret_cast<const char*>(p);
}
}
return nullptr;
}