0. 前言·
在阅读本篇文章前,你应该能先了解什么是字节序,什么是小端序,什么是大端序。
1. 正文·
leveldb中与编码相关的内容都在coding.h,coding.cc文件中。leveldb的编码都是采用小端序,编码一共分为固定长度编码和变长编码,且都分为32位和64位版本,我们先说固定长度编码。
2. 固定长度编码·
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 inline void EncodeFixed32 (char * dst, uint32_t value) { uint8_t * const buffer = reinterpret_cast <uint8_t *>(dst); buffer[0 ] = static_cast <uint8_t >(value); buffer[1 ] = static_cast <uint8_t >(value >> 8 ); buffer[2 ] = static_cast <uint8_t >(value >> 16 ); buffer[3 ] = static_cast <uint8_t >(value >> 24 ); } inline void EncodeFixed64 (char * dst, uint64_t value) { uint8_t * const buffer = reinterpret_cast <uint8_t *>(dst); buffer[0 ] = static_cast <uint8_t >(value); buffer[1 ] = static_cast <uint8_t >(value >> 8 ); buffer[2 ] = static_cast <uint8_t >(value >> 16 ); buffer[3 ] = static_cast <uint8_t >(value >> 24 ); buffer[4 ] = static_cast <uint8_t >(value >> 32 ); buffer[5 ] = static_cast <uint8_t >(value >> 40 ); buffer[6 ] = static_cast <uint8_t >(value >> 48 ); buffer[7 ] = static_cast <uint8_t >(value >> 56 ); }
一句话总结,低位字节放在低位内存,高位字节放在高位内存,也即小端序。
解码过程:
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 inline uint32_t DecodeFixed32 (const char * ptr) { const uint8_t * const buffer = reinterpret_cast <const uint8_t *>(ptr); return (static_cast <uint32_t >(buffer[0 ])) | (static_cast <uint32_t >(buffer[1 ]) << 8 ) | (static_cast <uint32_t >(buffer[2 ]) << 16 ) | (static_cast <uint32_t >(buffer[3 ]) << 24 ); } inline uint64_t DecodeFixed64 (const char * ptr) { const uint8_t * const buffer = reinterpret_cast <const uint8_t *>(ptr); return (static_cast <uint64_t >(buffer[0 ])) | (static_cast <uint64_t >(buffer[1 ]) << 8 ) | (static_cast <uint64_t >(buffer[2 ]) << 16 ) | (static_cast <uint64_t >(buffer[3 ]) << 24 ) | (static_cast <uint64_t >(buffer[4 ]) << 32 ) | (static_cast <uint64_t >(buffer[5 ]) << 40 ) | (static_cast <uint64_t >(buffer[6 ]) << 48 ) | (static_cast <uint64_t >(buffer[7 ]) << 56 ); }
按字节取即可。
3. 变长编码·
为什么需要变长编码? 考虑一个场景,假设现在我们只用在存放一个int类型的1, 这么简单的一个数字,我们却需要4个字节(一般情况下),要是可以压缩就好了。变长编码就是用来解决这个问题的,使用变长编码后,只用一个字节就可以保存这个1.
leveldb的变长编码也是采用小端序,同时在一个字节中,低7位表示的是实际数据,最高的1位用来表示,这个字节后序是否还有字节用来表示数字。举个例子:
假设要表示数字1000。 其二进制是111 1101000。
首先取出低7位:1101000, 高位先补0凑成一个字节, 0110 1000。
取出高3位: 111, 高位补0凑成一个字节, 0000 0111.
采用小端序存储这个数字: 0110 1000 0000 0111。
因为这里用到了两个字节表示一个数字,所以在第一个字节的最高位需要设置成1,表示第二个字节也是用来表示“1000(一千)”这个数字的。最终编码结果为:
1110 1000 0000 0111.
解码过程:
首先,读取第一个字节, 1110 1000, 最高位为1,表示后续还有字节表示这个数字,还原最高位位1后(0110 1000),再取一个字节(0000 0111), 因为0000 0111最高不为1,所以取字节结束。现在得到两个字节:
01110 1000 0000 0111
按照小端序还原:
0000 0111 0110 1000(二进制) ==> 1000(十进制)
知道了原理,来看代码吧。
变长编码函数签名:
1 2 3 4 5 char * EncodeVarint32 (char * dst, uint32_t value) ;char * EncodeVarint64 (char * dst, uint64_t value) ;
实现
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 char * EncodeVarint32 (char * dst, uint32_t v) { 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); }
按照前面讲的例子,这段代码不难理解,变量B用来设置一个字节的最高位,表示后续是否还有字节。
1 2 3 4 5 6 7 8 9 10 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); }
64位是32位的循环版本,两个代码表示同一个意思。
解码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 bool GetVarint32 (Slice* input, uint32_t * value) ;bool GetVarint64 (Slice* input, uint64_t * 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 void PutVarint32 (std::string* dst, uint32_t v) { char buf[5 ]; char * ptr = EncodeVarint32 (buf, v); dst->append (buf, ptr - buf); }
首先申请了一个5字节的buf,用来存储编码后的结果。为什么是5个字节?因为当32位的数值过大,比如 0xffffffff, 那么变长编码肯定需要5个字节才能表示。后面就很简单那了,执行编码,将编码结果保存在dst中。
4. PutLengthPrefixedSlice & GetLengthPrefixedSlice·
Put函数:
1 2 3 4 void PutLengthPrefixedSlice (std::string* dst, const Slice& value) { PutVarint32 (dst, value.size ()); dst->append (value.data (), value.size ()); }
对于一个字符串,这里首先将它的长度放到dst中,然后再将数值放入。
Get函数:
1 2 3 4 5 6 7 8 9 10 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中的长度信息去除,提出出原始string。
5. 总结·
本文分析了leveldb中与编码相关的知识,包含了固定长度编码与变长编码的原理与实现。这些内容虽然看似与leveldb无关,但是leveldb内部存储中却无时无刻不用到这些编码,所以我们现在都是在为后续看懂leveldb做铺垫。