计算机硬件有两种数据存储的方式:大端字节序和小端字节序
举例来说,数值0x2211
使用两个字节储存:高位字节是0x22
,低位字节是0x11
。
- 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
- 小端字节序:低位字节在前,高位字节在后,即以
0x1122
形式储存。
编码分为变长的VarInt和固定大小的FixedInt两种,每种分32位和64位。
变长编码
为了节省空间,设计了一种varint变长编码方式来表示整型,越小的数字所占用的字节数越少。不采用变长编码,存放一个int类型的1,一般情况下需要4个字节,采用变长编码后,仅需要1个字节就可以保存。leveldb的变长编码也是采用小端序
Varint 中的每个 byte 的最高位 bit 是标识位有特殊的含义。 如果该位为 1,表示后续的 byte 也是该数字的一部分,
如果该位为 0,则结束。 其他的 7 个 bit 都用来表示数字。
编码过程和解码过程举例:
编码过程:
假设一个待表示的数为1000,二进制表示为==111== 1101000
字节1:取最右边的7bit,1101000,高位补0凑成一个字节:0110 1000
字节2:剩下的是==111==,同样高位补0,0000 0111
字节1最高bit位标识为1,表示后续的byte也是该数字的一部分。
字节2最高bit位标识为0。
故最终的编码结果为:
1110 1000 0000 0==111==
解码过程:
读取第一个字节,发现其最高bit位为1,表示还有后续,与下一字节一同表示同一个数值。将最高bit位变为0后,第一个字节:0110 1000
下一个字节:0000 0==111==
按照小端序,字节交换后位置结果是:==111== 1101000 =》十进制为1000
leveldb中的实现
【编码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| char* EncodeVarint32(char* dst, uint32_t v) { 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); }
char* EncodeVarint64(char* dst, uint64_t v) { static const int B = 128; unsigned char* ptr = reinterpret_cast<unsigned char*>(dst); while (v >= B) { *(ptr++) = (v & (B-1)) | B; v >>= 7; } *(ptr++) = static_cast<unsigned char>(v); return reinterpret_cast<char*>(ptr); }
|
【解码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 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 unsigned char*>(p)); if ((result & 128) == 0) { *value = result; return p + 1; } } return GetVarint32PtrFallback(p, limit, value); }
const char* GetVarint32PtrFallback(const char* p, const char* limit, uint32_t* value) { 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) { result |= ((byte & 127) << shift); } else { result |= (byte << shift); *value = result; return reinterpret_cast<const char*>(p); } } return nullptr; }
|
leveldb封装了一个Put函数,便于用户调用
1 2 3 4 5 6
| void PutVarint32(std::string* dst, uint32_t v) { char buf[5]; char* ptr = EncodeVarint32(buf, v); dst->append(buf, ptr - buf); }
|
这里申请的buffer缓冲区大小为5个字节,因为变长编码的范围,对于32位(Varint32
)整形数经变长编码后占用15个Byte,小的数字用1个字节,大的数字0xffff ffff用5个字节。
对于64为(Varint64
)整形数经变长编码后占用110个Byte,小的数字用1个字节,大的数字用10个字节。
PutLengthPrefixedSlice函数
1 2 3 4 5 6 7
| void PutLengthPrefixedSlice(std::string* dst, const Slice& value) { PutVarint32(dst, value.size()); dst->append(value.data(), value.size()); }
|
对于一个数据,调用PutLengthPrefixedSlice函数,将他的长度和数值放入dst中。
GetLengthPrefixedSlice函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| bool GetLengthPrefixedSlice(Slice* input, Slice* result) { uint32_t len; if (GetVarint32(input, &len) && input->size() >= len) { *result = Slice(input->data(), len); input->remove_prefix(len); return true; } else { return false; } }
|
将编码后的string中的字符串长度移除,得到原始的字符串。
还有一同名的重载函数
1 2 3 4 5 6 7 8 9 10 11 12
| const char* GetLengthPrefixedSlice(const char* p, const char* limit,Slice* result) {
uint32_t len; p = GetVarint32Ptr(p, limit, &len); if (p == nullptr) return nullptr; if (p + len > limit) return nullptr; *result = Slice(p, len); return p + len; }
|
编码解码模块的类图