levelDB的编码格式
存储方式
当数据库获取到内存后,其需要将数据插入到内存中,并在适当的时候读取出来,如何高效地利用申请到的内存就是一个比较重要的问题。
在levelDB中,数据是按照下列方法存储的:
整数分为32位和64位定长整数
整数还使用变长整数存储
整数均为小端法存储
字符串采用长度前缀编码
变长整数与定长整数
所谓定长整数就是我们平时在C语言中定义的数据类型,其长度是始终不变的。
而变长整数则顾名思义,其编码的原理是只使用一个字节的低7位去存储数据,而最高位的用于做标识:当最高位为1时表示需要继续读取下一个字节。
如上图所示,这样变长整数就可以使用1-5字节去表示int32的所有整数,表面上看,虽然表示非常大的整数的时候变长整数编码会占用更多的空间,但是由于大整数出现的频率一般是比较小的,所以就普遍而言,使用变长整数会节省更多的内存。
字符串
字符串使用长度前缀编码,即在把字符串的长度放在字符串的前面,然后组合起来编码。
而同时,字符串长度使用变长整数进行编码,所以一个字符串的存储格式是32位变长整数编码字符串长度 + 字符串本身
使用长度前缀编码的方式,字符串能够编码任意字符(比如C语言不能在字符串中包含'\0'),同时,字符串的长度可以预先知道,所以有利于读写操作。另外,对于大部分字符串的长度都比较短的时候,并不会造成大量内存损失。
源码解析部分
源码位置: utils/coding.cc
utils/coding.h
头文件
首先,我们先来看一下编码解码和插入数据的头文件,这部分内容很简单,就是处理编码格式的一些方法。
其中要注意用到了一个类Slice
,这个类是对字符串的封装,比std::string
封装更加低级,所以效率会更高,后面的文章会细🔒它。
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
| void PutFixed32(std::string *dst, uint32_t value); void PutFixed64(std::string *dst, uint64_t value); void PutVarint32(std::string *dst, uint32_t value); void PutVarint64(std::string *dst, uint64_t value); void PutLengthPrefixedSlice(std::string *dst, const Slice &value);
bool GetVarint32(Slice *input, uint32_t *value); bool GetVarint64(Slice *input, uint64_t *value); bool GetLengthPrefixedSlice(Slice *input, Slice *result);
const char *GetVarint32Ptr(const char *p, const char *limit, uint32_t *v); const char *GetVarint64Ptr(const char *p, const char *limit, uint64_t *v);
const char *GetVarint32PtrFallback(const char *p, const char *limit, uint32_t *value);
int VarintLength(uint64_t v);
char *EncodeVarint32(char *dst, uint32_t value); char *EncodeVarint64(char *dst, uint64_t value);
inline void EncodeFixed32(char *dst, uint32_t value); inline void EncodeFixed64(char *dst, uint64_t value); inline uint32_t DecodeFixed32(const char *ptr); inline uint64_t DecodeFixed64(const char *ptr);
|
头文件只是给出了函数接口,下面我们就来进一步去分析各个函数。
实现文件
为了方便分析,我们将其分成:编码解码定长整数、编码解码变长整数、Put和Get方法。
这部分代码全部围绕编码解码来实现的,其中定长的编解码很简单,就不在此赘述了。关于变长整数的编解码,其关于编码和解码的相关函数都预留了类似迭代器一样的超尾元素(指针),这种形式的接口可以很方便的确定所处理数据的区间(变长整数的Put和Get相关函数都使用到了预留的超尾元素)。
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
|
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); }
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); }
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); }
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); }
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) { *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; }
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) { result |= ((byte & 127) << shift); } else { result |= (byte << shift); *value = result; return reinterpret_cast<const char *>(p); } } return nullptr; }
void PutFixed32(std::string *dst, uint32_t value) { char buf[sizeof(value)]; EncodeFixed32(buf, value); dst->append(buf, sizeof(buf)); }
void PutFixed64(std::string *dst, uint64_t value) { char buf[sizeof(value)]; EncodeFixed64(buf, value); dst->append(buf, sizeof(buf)); }
void PutVarint32(std::string *dst, uint32_t v) { char buf[5]; char *ptr = EncodeVarint32(buf, v); dst->append(buf, ptr - buf); }
void PutVarint64(std::string *dst, uint64_t v) { char buf[10]; char *ptr = EncodeVarint64(buf, v); dst->append(buf, ptr - buf); }
void PutLengthPrefixedSlice(std::string *dst, const Slice &value) { PutVarint32(dst, value.size()); dst->append(value.data(), value.size()); }
int VarintLength(uint64_t v) { int len = 1; while (v >= 128) { v >>= 7; len++; } return len; }
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; } }
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; } }
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; } }
|
其中,关于字符串的解码部分我认为还是很巧妙的,因为字符串编码后的长度在前,且长度是一个变长整数,所以解码时需要先提取出长度。正常情况下的写法可能是先对整个编码后字符串遍历,解析出前面的长度,记录下这个字符串长度以及变长整数的长度后再去处理后面的字符串。
但是levelDB中的处理方式是让变长解码函数解码多少就把输入中解码的这段数据给弄掉(修改了源字符串的指针),因为这部分需要解码的值已经被解析出来并返回了,也就不再需要了。
每个解码函数只处理字符串开头,并且处理完成后及时改变字符串的首地址(后移),这样就不需要记录这部分的状态并考虑处理细节了
能够这样做的原因就是通过传递超尾元素来标定界限,同时所有处理函数都以指针作为数据传递的方式(统一接口标准)。
而直接移动字符串指针很容易就引出另外一个问题:会不会内存泄漏呢?
答案是不会,因为内存并不由Slice管理,Slice只是对底层字符串做出了映射而已,其主要目的是用于传递,后续我们会对Slice进行解析。
再特意把这段代码贴出来
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
|
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; } }
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; } }
|
总结部分
inline void EncodeFixed32(char *dst,
uint32_t value) |
编码定长32位整数 |
inline void EncodeFixed64(char *dst,
uint64_t value) |
编码定长64位整数 |
inline uint32_t DecodeFixed32(const char
*ptr) |
解码32位定长整数 |
inline uint64_t DecodeFixed64(const char
*ptr) |
解码64位定长整数 |
char EncodeVarint32(char dst,
uint32_t v) |
编码32位变长整数 |
char EncodeVarint64(char dst,
uint64_t v) |
编码64位变长整数 |
inline const char
GetVarint32Ptr(const char p, const char limit, uint32_t
value) |
解码32位变长整数 |
const char
GetVarint32PtrFallback(const char p, const char
limit,uint32_t value) |
解码32位变长整数中大于127的数字,被GetVarint32Ptr调用 |
const char GetVarint64Ptr(const char
p, const char limit, uint64_t value) |
解码64位变长整数的函数 |
void PutFixed32(std::string *dst,
uint32_t value) |
把32位定长转换成字符串 |
void PutFixed64(std::string *dst,
uint64_t value) |
把64位定长转化成字符串 |
void PutVarint32(std::string *dst,
uint32_t v) |
把32位变长转化成字符串 |
void PutVarint64(std::string *dst,
uint64_t v) |
把64位变长转化成字符串 |
void PutLengthPrefixedSlice(std::string
*dst, const Slice &value) |
把slice转化成字符串存储 |
int VarintLength(uint64_t v) |
计算变长整数的长度 |
bool GetVarint32(Slice input,
uint32_t value) |
从字符串中获取32位变长 |
bool GetVarint64(Slice input,
uint64_t value) |
从字符串中获取64位变长 |
bool GetLengthPrefixedSlice(Slice
input, Slice result) |
解码出字符串 |
编码解码部分使用到了很多位级编程,这部分都很有意思,可以做到四两拨千斤的作用。