LevelDB原始碼分析之一:coding
阿新 • • 發佈:2019-02-07
LevelDB預設使用的是小端位元組序儲存,低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。
編碼分為變長的EncodeVarint和固定大小的EncodeFixed兩種,每種又分32位和64位。
一.EncodeFixed
解碼函式DecodeFixed32和DecodeFixed64是編碼的逆過程,也很簡單。
二.EncodeVarint
為什麼要把整型(int)編碼成變長整型(varint)呢?是為了儘可能的節約儲存空間。
varint是一種緊湊的表示數字的方法,它用一個或多個位元組來表示一個數字,值越小的數字使用越少的位元組數。比如int32型別的數字,一般需要4個位元組。但是採用Varint,對於很小的int32型別的數字,則可以用1個位元組來表示。當然凡事都有好的也有不好的一面,採用varint表示法,大的數字則可能需要5個位元組來表示。從統計的角度來說,一般不會所有訊息中的數字都是大數,因此大多數情況下,採用varint後,可以用更小的位元組數來表示數字資訊。
varint中的每個位元組的最高位(bit)有特殊含義,如果該位為1,表示後續的位元組也是這個數字的一部分,如果該位為0,則結束。其他的7位(bit)都表示數字。7位能表示的最大數是127,因此小於128的數字都可以用一個位元組表示。大於等於128的數字,比如說300,會用兩個位元組在記憶體中表示為:
低 高
1010 1100 0000 0010
實現過程如下:
300的二進位制為100101100,取低7位也就是010 1100放在記憶體低位元組中,由於第二個位元組也是數字的一部分,因此記憶體低位元組的最高位為1,則完整的記憶體低位元組為1010 1100。300的高2位也就是10放到記憶體的高位元組中,因為數字到該位元組結束,因此該位元組包括最高位的其他6位都用0填充,則完整的記憶體高位元組為0000 0010。
正常情況下,int需要32位,varint用一個位元組的最高位作為標識位,所以,一個位元組只能儲存7位,如果整數特別大,可能需要5個位元組才能存放(5*8-5(標誌位)>32),下面if語句的第五個分支就是處理這種情況。
編碼分為變長的EncodeVarint和固定大小的EncodeFixed兩種,每種又分32位和64位。
一.EncodeFixed
void EncodeFixed32(char* buf, uint32_t value) { #if __BYTE_ORDER == __LITTLE_ENDIAN memcpy(buf, &value, sizeof(value)); #else buf[0] = value & 0xff; buf[1] = (value >> 8) & 0xff; buf[2] = (value >> 16) & 0xff; buf[3] = (value >> 24) & 0xff; #endif }
這兩個函式非常簡單,功能就是判斷系統是否為小端儲存,如果是,直接將value拷貝到buf,如果不是,則將value轉換為小端位元組序儲存到buf中。void EncodeFixed64(char* buf, uint64_t value) { #if __BYTE_ORDER == __LITTLE_ENDIAN memcpy(buf, &value, sizeof(value)); #else buf[0] = value & 0xff; buf[1] = (value >> 8) & 0xff; buf[2] = (value >> 16) & 0xff; buf[3] = (value >> 24) & 0xff; buf[4] = (value >> 32) & 0xff; buf[5] = (value >> 40) & 0xff; buf[6] = (value >> 48) & 0xff; buf[7] = (value >> 56) & 0xff; #endif }
解碼函式DecodeFixed32和DecodeFixed64是編碼的逆過程,也很簡單。
二.EncodeVarint
為什麼要把整型(int)編碼成變長整型(varint)呢?是為了儘可能的節約儲存空間。
varint是一種緊湊的表示數字的方法,它用一個或多個位元組來表示一個數字,值越小的數字使用越少的位元組數。比如int32型別的數字,一般需要4個位元組。但是採用Varint,對於很小的int32型別的數字,則可以用1個位元組來表示。當然凡事都有好的也有不好的一面,採用varint表示法,大的數字則可能需要5個位元組來表示。從統計的角度來說,一般不會所有訊息中的數字都是大數,因此大多數情況下,採用varint後,可以用更小的位元組數來表示數字資訊。
varint中的每個位元組的最高位(bit)有特殊含義,如果該位為1,表示後續的位元組也是這個數字的一部分,如果該位為0,則結束。其他的7位(bit)都表示數字。7位能表示的最大數是127,因此小於128的數字都可以用一個位元組表示。大於等於128的數字,比如說300,會用兩個位元組在記憶體中表示為:
低 高
1010 1100 0000 0010
實現過程如下:
300的二進位制為100101100,取低7位也就是010 1100放在記憶體低位元組中,由於第二個位元組也是數字的一部分,因此記憶體低位元組的最高位為1,則完整的記憶體低位元組為1010 1100。300的高2位也就是10放到記憶體的高位元組中,因為數字到該位元組結束,因此該位元組包括最高位的其他6位都用0填充,則完整的記憶體高位元組為0000 0010。
正常情況下,int需要32位,varint用一個位元組的最高位作為標識位,所以,一個位元組只能儲存7位,如果整數特別大,可能需要5個位元組才能存放(5*8-5(標誌位)>32),下面if語句的第五個分支就是處理這種情況。
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)) { //如果v小於128
*(ptr++) = v;
} else if (v < (1<<14)) { //如果v小於16384,假如v=300(0000 0001 0010 1100),先和128(0000 0000 1000 0000)按位或,得到0000 0001 1010 1100,把低8位(1010 1100)給記憶體低位元組。
*(ptr++) = v | B;
*(ptr++) = v>>7; //把300(0000 0001 0010 1100)右移7位得到000 0000 0000 0001 0,給記憶體高位元組。
} 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);
}
對於64位整型,我們最多需要10個位元組(10*8-10(標誌位)>64),如果像EncodeVarint32一樣寫程式碼,需要有10個if分支,大牛肯定沒這麼勤快。其實EncodeVarint32也可以像EncodeVarint64這麼寫。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);
}
下面的函式是計算整型編碼後的長度,也就是varint的長度。
int VarintLength(uint64_t v) {
int len = 1;
while (v >= 128) {
v >>= 7;
len++;
}
return len;
}
三.varint解碼
解了編碼的原理,再來看解碼就很輕鬆了,直接呼叫GetVarint32Ptr函式,該函式處理value < 128的情況,即varint只佔一個位元組的情況,對於varint 大於一個位元組的情況,GetVarint32Ptr呼叫GetVarint32PtrFallback來處理。
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);
}
在GetVarint32Ptr和GetVarint32PtrFallback函式中,引數p是指向一個包含varint的字串,limit在呼叫的時候都是賦值為limit= p + 5, 這是因為varint最多佔用5個位元組。value用於儲存返回的int值。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 unsigned char*>(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 NULL;
}
64位的解碼與32位的類似。