上一篇文章簡(jiǎn)單地描述了一下Protocol Buffer的用法,懂得了上篇文章的用法之后,相信大家正常使用Protocol Buffer已經(jīng)沒(méi)有太大問(wèn)題了,沒(méi)有覆蓋到的細(xì)節(jié)用法,在網(wǎng)上搜一下應(yīng)該也不難找到。然而知其表而復(fù)知其理,知其華而復(fù)知其實(shí),所以今天這篇文章給大家介紹一下Protocol Buffer的一些編碼原理,可以用來(lái)解釋為什么序列化之后的數(shù)據(jù)包比XML和JSON小那么多。
一、Varint編碼
Protocol Buffer 在對(duì)數(shù)字進(jìn)行編碼使用的是Varint的編碼方式。我們使用的int類(lèi)型,一般需要占用4個(gè)字節(jié)的長(zhǎng)度,采用Varint編碼,可以用一個(gè)字節(jié)或多個(gè)字節(jié)來(lái)表示一個(gè)數(shù)字,值越小的數(shù)字占用越少的字節(jié)數(shù)。
Varint的每個(gè)byte的最高位bit用來(lái)表示后續(xù)的byte是不是也屬于該數(shù)字的一部分。如果最高位為0,則表示該byte是這個(gè)數(shù)字的最后一個(gè)byte,如果最高位為1,表示后面的一個(gè)byte仍然是該數(shù)字的一部分。以無(wú)符號(hào)數(shù)為例,對(duì)于0-127,只需要一個(gè)字節(jié)表示,最高位為0,后7位表示值。如果一個(gè)數(shù)字需要多個(gè)字節(jié)去表示,這里面有個(gè)需要注意的地方:高位的數(shù)字是放在后面的字節(jié)里面的。
example:300的Varint編碼為1010 1100,0000 0010,去掉兩個(gè)最高位為010 1100,000 0010,將后面的字節(jié)放到前面為000 0010 010 1100,也就是100101100,換算成十進(jìn)制就是300了。
對(duì)于有符號(hào)的數(shù)字,Protocol Buffer 還會(huì)采用ZigZag編碼,這樣無(wú)論正數(shù)還是負(fù)數(shù),絕對(duì)值小的時(shí)候使用Varint編碼占用的字節(jié)數(shù)就會(huì)更少。ZigZag編碼大家從下面一張圖就可以看明白原理了。

二、消息體編碼
我們平時(shí)用JSON傳遞數(shù)據(jù),Java對(duì)象序列化為JSON串之后一般會(huì)是下面這個(gè)樣子。
{"code":200,"name":"magiccoder","sex":"man","index":"www.magiccoder.net"}
可能平時(shí)一直這么用,大家都已經(jīng)習(xí)以為常了,但是這個(gè)對(duì)象的數(shù)據(jù)包在頻繁傳輸?shù)倪^(guò)程中,key值其實(shí)是一直重復(fù)傳輸?shù)模琸ey值僅僅是一個(gè)標(biāo)志位,只要客戶端和服務(wù)端約定之后采用同樣的key值就可以,如果把key值直接約定為從1開(kāi)始的自然數(shù),肯定是比上面的JSON串的長(zhǎng)度要短的。甚至我們可以這么想,把需要傳輸?shù)淖侄沃蛋凑占s定的順序排列下來(lái),做好字段之間的分界,就連key值都不需要了。
Protocol Buffer使用的是Tag-Length-value的形式來(lái)組織數(shù)據(jù)的,Length字段非必需。
-
Tag值有兩個(gè)作用,一是標(biāo)志該字段在message中是第幾個(gè)值,二是標(biāo)明該字段的數(shù)據(jù)類(lèi)型。按照官方文檔的說(shuō)法,他們用Tag中的3個(gè)bit來(lái)表示數(shù)據(jù)類(lèi)型。這樣對(duì)于固定長(zhǎng)度的數(shù)據(jù)字段,在反序列的時(shí)候,通過(guò)數(shù)據(jù)類(lèi)型的長(zhǎng)度就可以正確地界定和取出數(shù)據(jù)。對(duì)于Varint類(lèi)型的字段,程序也可以按照Varint的編碼方式將value值計(jì)算出來(lái)。下面這張圖片是從官方截取的,大家可以看到一共有5種數(shù)據(jù)類(lèi)型,那么就需要3個(gè)二進(jìn)制去表示。Tag值采用Varints編碼,如果Tag占用一個(gè)字節(jié),1個(gè)bit用來(lái)表示MSB,3個(gè)bit用來(lái)表示數(shù)據(jù)類(lèi)型,還有4個(gè)bit用來(lái)表示字段在message中的位置,最多可以表示16個(gè)字段,一般可以滿足大部分需要了。
wiretype.png length字段表示可變長(zhǎng)度的字段時(shí)用到的,屬于可選字段。對(duì)于上圖中type序號(hào)為2的類(lèi)型,字段長(zhǎng)度不是固定的,像大家熟知的String類(lèi)型,長(zhǎng)度變化范圍太大,單靠數(shù)據(jù)類(lèi)型已經(jīng)沒(méi)法界定它的范圍了。Protocol Buffer 使用的策略是在Tag值之后用一個(gè)length字段來(lái)標(biāo)志value的長(zhǎng)度,這樣也就可以正確的界定字段的范圍啦。
對(duì)于repeated字段和optional字段,如果沒(méi)有設(shè)置,序列化的時(shí)候就會(huì)被直接忽略,不會(huì)參與消息體編碼。
三、Protocol Buffer 的其他優(yōu)點(diǎn)
筆者認(rèn)為Protocol Buffer除了數(shù)據(jù)包小,序列化和反序列化速度快,還有下面兩個(gè)很不錯(cuò)的優(yōu)點(diǎn):
- 語(yǔ)言互通
現(xiàn)在很多編程語(yǔ)言都已經(jīng)支持Protocol Buffer 了,到筆者寫(xiě)這篇文章的時(shí)候,Java、C++、C#、python、Golang都已經(jīng)支持了。像我們之前做通訊軟件的時(shí)候一樣,后端使用Golang,客戶端使用Java,兩邊直接使用同一份proto文件描述的message,就可以自動(dòng)生成通信用的類(lèi)或者結(jié)構(gòu)體,可以減少非常多的麻煩??蛻舳烁蠖唆[得不愉快,大部分時(shí)候都是因?yàn)橥ㄐ诺南Ⅲw出問(wèn)題。 - 向后兼容
軟件發(fā)布新版本的時(shí)候,經(jīng)常會(huì)出現(xiàn)這樣的情況,舊版的軟件跟新的軟件在通信協(xié)議方面不兼容。如果使用Protocol Buffer的話,新通信協(xié)議中添加的新字段不會(huì)影響到舊版本的軟件,它會(huì)把自己不具有的字段忽略掉。
附上官方網(wǎng)站,上面介紹的還是很全面的,英文還不錯(cuò)的同學(xué)可以看一下:
https://developers.google.com/protocol-buffers/
2017年第一周,最近玩的太high了,所以過(guò)了這么久才更新這篇文章。
最后祝大家2017年身體健康、工作順利、心想事成、笑口常開(kāi)~
