LevelDB內部通過采用變長編碼,對數據進行壓縮來減少存儲空間,采用CRC進行數據正確性校驗。下面就對varint編碼進行學習。
傳統的integer是以32位來表示的,存儲需要4個字節,當如果整數大小在256以內,那么只需要用一個字節就可以存儲這個整數,這樣就可以節省3個字節的存儲空間,Google varint就是根據這種思想來序列化整數的
無符號
Varint 是一種緊湊的表示數字的方法。它用一個或多個字節來表示一個數字,值越小的數字使用越少的字節數。這能減少用來表示數字的字節數。
Varint 中的每個 byte 的最高位 bit 有特殊的含義,如果該位為 1,表示后續的 byte 也是該數字的一部分,如果該位為 0,則結束。其他的 7 個 bit 都用來表示數字。因此小於 128 的數字都可以用一個 byte 表示。大於 128 的數字,會用兩個字節。
例如整數1的表示,僅需一個字節:
0000 0001
例如300的表示,需要兩個字節:
1010 1100 0000 0010
采用 Varint,對於很小的 int32 類型的數字,則可以用 1 個 byte 來表示。當然凡事都有好的也有不好的一面,采用 Varint 表示法,大的數字則需要 5 個 byte 來表示。從統計的角度來說,一般不會所有的消息中的數字都是大數,因此大多數情況下,采用 Varint 后,可以用更少的字節數來表示數字信息。
下圖演示了 Google Protocol Buffer 如何解析兩個 bytes。注意到最終計算前將兩個 byte 的位置相互交換過一次,這是因為 Google Protocol Buffer 字節序采用 little-endian 的方式。
有符號
如果使用int32/int64表示一個負數,該字段的值無論是-1還是-2147483648,其編碼后長度將始終為10個字節,就如同對待一個很大的無符號整型一樣。反之,如果使用的是sint32/sint64,Protocol Buffer將會采用ZigZag編碼方式,其編碼后的結果將會更加高效。
這里簡單講述一下ZigZag編碼,該編碼會將有符號整型映射為無符號整型,以便絕對值較小的負數仍然可以有較小的varint編碼值,如-1。下面是ZigZag對照表:
其公式為:
(n << 1) ^ (n >> 31) //sint32
(n << 1> ^ (n >> 63) //sint64
需要補充說明的是,Protocol Buffer在實現上述位移操作時均采用的算術位移,因此對於(n >> 31)和(n >> 63)而言,如果n為負值位移后的結果就是-1,否則就是0。
注:簡單解釋一下C語言中的算術位移和邏輯位移。他們的左移操作都是相同的,即低位補0,高位直接移除。不同的是右移操作,邏輯位移比較簡單,高位全部補0。而算術位移則需要視當前值的符號位而定,補進的位和符號位相同,即正數全補0,負數全補1。換句話說,算術位移右移時要保證符號位的一致性。在C語言中,如果使用 int變量位移時就是算術位移,uint變量位移時是邏輯位移。
實現可以參考下面文章