之前那篇文章,講過Json里的序列化結果為: { "name":"chenpp","age":21} -- 一共26個字節,而想要將其進行進一步壓縮,就需要去掉一些冗余的字節
思路:1)能不能去掉定義屬性(約定1=name,2=age) 約定了字段,約定了類型 去除分隔符(引號,冒號,逗號之類的)
2)壓縮數字,因為日常經常使用到的都是一些比較小的數字,一共int占4個字節,但實際有效的字節數沒有那么多
把英文轉化成數字(ASCII),並對數字進行壓縮
protobuf里使用到了兩種壓縮算法:varint和zigzag算法
varint算法
這是針對無符號整數,一種壓縮方式
壓縮方法:
1.對一個無符號整數,將其換算成二進制
2.從右往左取7位,然后在最高位補1
3.一直繼續,直到取到最后一個有意義的字節(在最后一個有意義的字節上最高位補0)
4.先取到的字節排在后取到的字節前面,得到一個新的字節,轉換成十進制就是壓縮的結果
以:500為例:其實有意義的就是2個字節
0000 0001 1111 0100= 2^2+2^4+2^5+2^6+2^7+2^8 = 4+16+32+64+128+256 = 500
按照其壓縮方式得到的新的二進制字節為:
1111 0100 | 0000 0011
1111 0100 代表的是負數,使用補碼(正數取反+1),並且最高位符號位為1
轉化后: 先 -1為 1111 0011 取反 0000 1100 = 12
計算出來就是 -12 3
字符如何轉化成數字編碼
對於英文字母,這里的name字段,使用ASCII碼對照表查找對應的數字
chenpp: 對應的ASCII碼
c-99 h-104 e-101 n-110 p-112
按照varint的算法 取7位補最高位為1(最后一個字節最高位補0)
對於小於127(2^7-1=127)的數字,其有效字節只有1位,壓縮的時候最高位補0,故壓縮之前和壓縮之后的數字沒有變化
protobuf的存儲格式
protobuf采用T-L-V的格式進行存儲
[Tag | length | value ]
l其中length為可選, 但是string必須有length(這樣在反序列化的時候程序才知道該字符串從哪里開始到哪里結束), 而int是不需要length的
Tag:字段標識符,用於標識字段 其值等於
field_number(當前字段的編號,第幾個字段)<<3|wire_type(int64/int32/可變長度string)
Length:Value的字節長度 (string需要有,int不需要)
Value:消息字段經過編碼后的值
Age:int32 2<<3|0 = 16
Name: string 1<<3|2 = 10
在反序列化的時候根據tag mod 8 的余數判斷對應的wireType,從而知道該字段對應的存儲方式和編碼方式
對應User(name="chenpp",age=21)按照varint進行壓縮后其序列化結果為:
c-99 h-104 e-101 n-110 p-112
String:tag-length-value
Int32:tag-value
10 6 99 104 101 110 112 112 16 21
Tag – length - c - h – e - n - p - p - Tag - Value
根據上述壓縮算法可知:對於int32/int64,value有且只有最后一個字節為正數,故當遇到第一個為正數的字節時就知道其value值已經獲取完畢,所以對於int32類型的字段,不需要length,只需要tag和value就足夠了
ZigZag算法
在計算機中,負數會被表示為很大的整數,因為負數的符號位在最高位,如果使用varint算法進行壓縮的話會需要 32/7 ~ 5個字節,反
而加大了空間的開銷.故在protobuf中對於有符號整數會使用sint32/sint64來表示。protobuf中負數的壓縮方式是先使用ZigZag算把有符號數(無論數值是正數還是負數,都會進行一次壓縮計算)轉化為無符號數,再使用varint進行壓縮
ZigZag算法的思路:
負數之所以不好壓縮:一個原因是因為其最高位為1,另一個原因是對於絕對值比較小的負數,其正數會有很多的前導零,那么在使用補碼表示負數的時候(取反+1),會導致負數會有很多的前導1,使得無法壓縮
所以ZigZag采用的辦法就是:先將符號位從最高位移動到最低位,其余數字均往前移動1位;然后再對所有的數字(符號位除外)進行取反,這樣得到的計算結果就是一個可以壓縮的數字(符號位不占據最高位,而小絕對值的數值由於取反操作其前導1都變為了前導0)
比方說-300:
其對應的正數的原碼為: 0000 0000 0000 0000 0000 0001 0010 1100
取反: 1111 1111 1111 1111 1111 1110 1101 0011
再+1: 1111 1111 1111 1111 1111 1110 1101 0100 (-300)
移動符號位之后: 1111 1111 1111 1111 1111 1101 1010 1001
取反:0000 0000 0000 0000 0000 0010 0101 0111
計算后為0010 0101 0111 = 599
在ZigZag算法里也是使用這種思路對有符號整數進行壓縮的,將其轉化成表達式就是
Sint32: (n<<1)^ (n>>31)
Sint64: (n<<1)^(n>>63)
當n為正數時,n>>31為0000 0000 0000 0000 0000 0000 0000 0000;當n為負數時,n>>31為1111 1111 1111 1111 1111 1111 1111 1111
(n>>31)與(n<<1)進行異或之后,如果n為正數,(n>>31)^(n<<1) =n<<1;如果n為負數,其計算結果和上述所說的最高位移動到最后,然后取反效果是一樣的(n<<1補的最低位為0和n>>31異或運算之后一定為1,而其他位上與1做異或運算相當於取反),這樣一來就可以使用varint進行壓縮計算了
對-300的ZigZag計算結果:599 使用varint算法進行壓縮
得到1101 0111 0000 0100
其結果為:-41 4
到此,protobuf的壓縮原理就介紹完了
https://blog.csdn.net/qq_35448165/article/details/99473027
