ProtocalBuffer_數據結構(轉thanks)

      1、簡單消息編碼佈局:
      讓咱們先看一下下面的消息定義示例:
      message Test1 {
          required int32 a = 1;
      }
      假設咱們在應用程序中將字段a的值設置爲150(十進制),此後再將該對象序列化到Binary文件中,你能夠看到文件的數據爲:
      08 96 01
      這3個字節的含義又是什麼呢?它們又是按照什麼樣的編碼規則生成的呢?讓咱們拭目以待。
    
      2、Base 128 Varints:
      在理解Protocol Buffer的編碼規則以前,你首先須要瞭解varints。varints是一種使用一個或多個字節表示整型數據的方法。其中數值自己越小,其所佔用的字節數越少。
      在varint中,除了最後一個字節以外的每一個字節中都包含一個msb(most significant bit)設置(使用最高位),這意味着其後的字節是否和當前字節一塊兒來表示同一個整型數值。而字節中的其他七位將用於存儲數據自己。由此咱們能夠簡單的解 釋一下Base 128,一般而言,整數數值都是由字節表示,其中每一個字節爲8位,即Base 256。然而在Protocol Buffer的編碼中,最高位成爲了msb,只有後面的7位存儲實際的數據,所以咱們稱其爲Base 128(2的7次方)。
      好比數字1,它自己只佔用一個字節便可表示,因此它的msb沒有被設置,如:
      0000 0001
      再好比十進制數字300,它的編碼後表示形式爲:
      1010 1100 0000 0010
      對於Protocol Buffer而言又是如何將上面的字節佈局還原成300呢?這裏咱們須要作的第一步是drop掉每一個字節的msb。從上例中能夠看出第一個字節(1010 1100)的msb(最高位)被設置爲1,這說明後面的字節將連同該字節表示同一個數值,而第二個字節(0000 0010)的msb爲0,所以該字節將爲表示該數值的最後一個字節了,後面若是還有其餘的字節數據,將表示其餘的數據。
      1010 1100 0000 0010
      -> 010 1100 000 0010
      上例中的第二行已經將第一行中每個字節的msb去除。因爲Protocol Buffer是按照Little Endian的方式進行數據佈局的,所以咱們這裏須要將兩個字節的位置進行翻轉。
      010 1100 000 0010
      -> 000 0010 010 1100           //翻轉第一行的兩個字節
      -> 100101100                         //將翻轉後的兩個字節直接鏈接並去除高位0
      -> 256 + 32 + 8 + 4 = 300    //將上一行的二進制數據換算成十進制,其值爲300
    
      3、消息結構:
      Protocol Buffer中的消息都是由一系列的鍵值對構成的。每一個消息的二進制版本都是使用標籤號做爲key,而每個字段的名字和類型均是在解碼的過程當中根據目標 類型(反序列化後的對象類型)進行配對的。在進行消息編碼時,key/value被鏈接成字節流。在解碼時,解析器能夠直接跳過不識別的字段,這樣就能夠 保證新老版本消息定義在新老程序之間的兼容性,從而有效的避免了使用older消息格式的older程序在解析newer程序發來的newer消息時,一 旦遇到未知(新添加的)字段時而引起的解析和對象初始化的錯誤。最後,咱們介紹一下字段標號和字段類型是如何進行編碼的。下面先列出Protocol Buffer能夠支持的字段類型。
數組

Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimited string, bytes, embedded messages, packed repeated fields
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, float

      因爲在編碼後每個字段的key都是varint類型,key的值是由字段標號和字段類型合成編碼所得,其公式以下:
      field_number << 3 | field_type
      由此看出,key的最後3個bits用於存儲字段的類型信息。那麼在使用該編碼時,Protocol Buffer所支持的字段類型將不會超過8種。這裏咱們能夠進一步計算出Protocol Buffer在一個消息中能夠支持的字段數量爲2的29次方減一。如今咱們再來回顧一下以前給出的Test1消息被序列化後的第一個字節08的由來。
      0000 1000
      -> 000 1000                  //drop掉msb(最高位)
      最低的3位表示字段類型,即0爲varint。咱們再將結果右移3位( >> 3),此時獲得的結果爲1,即字段a在消息Test1中的標籤號。經過這樣的結果,Protocol Buffer的解碼器能夠獲悉當前字段的標籤號是1,其後所跟隨數據的類型爲varint。如今咱們能夠繼續利用上面講到的知識分析出後兩個字節(96 01)的由來。
      96 01 = 1001 0110 0000 0001
          -> 001 0110 000 0001   //drop兩個字節的msb
          -> 000 0001 001 0110   //翻轉高低字節
          -> 10010110                   //去掉最高位中沒用的0
          -> 128 + 16 + 4 + 2 = 150
    
      4、更多的值類型:
      1. 有符號整型
      如前所述,類型0表示varint,其中包含int32/int64/uint32/uint64/sint32/sint64/bool/enum。在 實際使用中,若是當前字段能夠表示爲負數,那麼對於int32/int64和sint32/sint64而言,它們在進行編碼時將存在着較大的差異。若是 使用int32/int64表示一個負數,該字段的值不管是-1仍是-2147483648,其編碼後長度將始終爲10個字節,就如同對待一個很大的無符 號整型同樣。反之,若是使用的是sint32/sint64,Protocol Buffer將會採用ZigZag編碼方式,其編碼後的結果將會更加高效。
      這裏簡單講述一下ZigZag編碼,該編碼會將有符號整型映射爲無符號整型,以便絕對值較小的負數仍然能夠有較小的varint編碼值,如-1。下面是ZigZag對照表:
佈局

Signed Original Encoded As
0 0
-1 1
1 2
-2 3
2147483647 4294967294
-2147483648 4294967295

      其公式爲:
      (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變量位移時是邏輯位移。
      2. Non-varint數值型
      double/fixed64始終都佔用8個字節,float/fixed32始終佔用4個字節。
      3. Strings
      其類型值爲2,key信息以後是字節數組的長度信息,最後在緊隨指定長度的實際數據值信息。如:
      message Test2 {
          required string b = 2;
      }
      如今咱們設置b的值爲"testing"。其編碼後數據以下:
      12 07 74 65 73 74 69 6E 67
      第一個字節0x12表示key,經過解碼能夠獲得字段類型2和字段標號2。第二個字節07表示testing的長度。後面7個紅色高亮的字節則表示testing。
    
      5、嵌入消息:
      這裏是一個包含嵌入消息的消息定義。
      message Test3 {
          required Test1 c = 3;
      }
      此時咱們先將Test1的a字段值設置爲150,其編碼結果以下:
      1A 03 08 96 01
      從上面的結果能夠看出08 96 01和以前直接編碼Test1時是徹底一致的,只是在前面增長了key(字段類型 + 標號)和長度信息。新增信息的解碼方式和含義與前面的Strings徹底相同,這裏再也不重複解釋了。
    
      6、Packed Repeated Fields:
      Protocol Buffer從2.1.0版本開始引入了[pack = true]的 字段級別選項。若是設置該選項,那麼元素數量爲0的repeated字段將不會被編碼,不然數組中的全部元素會被編碼成一個單一的key/value形 式。畢竟數組中的每個元素都具備相同的字段類型和標號。該編碼形式,對包含較小值的整型元素而言,優化後的編碼結果能夠節省更多的空間。如:
      message Test4 {
          repeated int32 d = 4 [pack=true];
      }
      這裏咱們假設d字段包含3個元素,值分別爲3,270,86942。編碼結果以下:
      22             //key (字段標號4,類型爲2)
      06             //數據中全部元素所佔用的字節數量
      03             //第一個元素(varint 3)
      8E 02        //第二個元素(varint 270)
      9E A7 05  //第三個元素(varint 86942)
    
      7、字段順序:
      在.proto文件中定義消息的字段標號時,能夠是不連續的,可是若是將其定義爲連續遞增的數值,將得到更好的編碼和解碼性能。
   
性能

相關文章
相關標籤/搜索