Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Protocol Buffers Encoding

Protocol Buffers Encoding

編輯:關於Android編程

本文檔介紹了protocol buffer消息的二進制格式。在你的應用程序中使用protocol buffers的時候,你不需要理解這些,但是對於你想知道不同的protocol buffer格式如何影響你的消息編碼的大小是非常有用的。

 

一個簡單的消息

假設你有如下的一個簡單的消息定義:

message Test1 {
  required int32 a = 1;
} 在一個應用程序中,你創建了一個Test1消息並且設置a的值為150。然後你將這個消息序列化到一個輸出流。如果你想檢車編碼後的消息,你將看到如下的三個字節:

08 96 01

Base 128 Varints

為了理解protocol buffer的編碼,首先需要理解varints。Varints是一個將整數序列化成一個或多個字節的方法。數字越小占用的字節數也越小。

一個varint中的每個字節,除了最後一個字節,都要設置一個most significant bit (msb)用來標識接下來還有字節。每個字節的低7位用來存儲7個字節表示的數字的二進制補碼,least significant group first。

例如,數字1,它是單個字節,所以不需要設置msb:

0000 0001如果是300,就要復雜一點:

1010 1100 0000 0010你是如何推斷這是300呢?首先你丟掉每個字節的msb,因為msb只是用來告訴我們是否到達數字的末尾:

1010 1100 0000 0010
→ 010 1100  000 0010接著反轉兩組7位字節,因為varints存儲數字的原則是the least significant group first。然後可以計算最終值了:

000 0010  010 1100
→  000 0010 ++ 010 1100
→  100101100
→  256 + 32 + 8 + 4 = 300

消息結構

一個protocol buffer消息是一系列的key-value對。一個消息的二進制版本就是使用字段的數字作為key——每個字段的名字和聲明的類型在解碼結束的時候通過引用消息的類型定義(比如.proto文件)來決定。

當一個消息被編碼的時候,keys和values被連結成一個字節流。當消息被解碼的時候,解析器需要跳過它不能識別的字段。這樣的話,新的字段可以被添加到一個消息中,而不會中斷不知道這些新字段的舊程序。每個key-value對中的key實際上包含了兩個值——來自.proto文件的字段數字,加上一個類型用來提供足夠的信息決定接下來值得長度。

可用的wire類型如下:


int32, int64, uint32, uint64, sint32, sint64, bool, enum Type Meaning Used For
0 Varint  
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,其值為(field_number<<3)|wire_type——換句話說,數字的最後三個bit用來存儲wire type的。

現在再看一個簡單的例子。你現在已經知道流中第一個數字總是一個varint key,這裡是08,或者(丟掉msb):

000 1000你通過後三位bit得到wire type是0,然後右移三位得到字段數字是1。所以你現在知道tag是1,隨後的值是一個varint。使用前面的varint解碼知道,我們可以知道隨後的兩個字節存儲的值是150。

96 01 = 1001 0110  0000 0001
       → 000 0001  ++  001 0110 (drop the msb and reverse the groups of 7 bits)
       → 10010110
       → 2 + 4 + 16 + 128 = 150


更多的值類型

有符號整數

所有的protocol buffer類型為0的被編碼成varints。但是,當編碼負數的時候,有符號整型(sint32和sint64)和標准整型(int32和int64)是有很大區別的。如果你使用int32或int64作為一個負數的類型,其對應的varint總是10個字節的長度——它像一個非常大的無符號整數被對待。如果你使用有符號類型,其對應的varint會使用ZigZag編碼,這樣會更高效。

ZigZag編碼將有符號整數映射到無符號整數,所以擁有較小絕對值的數字(比如-1)也擁有較小的varint編碼值。其做法是正數和負數之間反復地"zig-zags",所以-1編碼成1,1編碼成2,-2編碼成3,依此類推,如下表所示:


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


換句話說,每一個值n對於sint32被編碼如下:

(n << 1) ^ (n >> 31)對於sint64,被編碼:

(n << 1) ^ (n >> 63)注意(n>>31)部分是一個算術移位。換句話說,這個移位的結果數字要麼所有的位全0(n是正數),要麼全1(n是負數)。

當sint32或sint64被解析的時候,其值被解碼成有符號的原始值。

 

非varint數字

非varint數字類型也非常簡單——double和fixed64其wire type為1,告訴解析器獲取一個固定的64位的數據;float和fixed32其wire type為5,告訴解析器獲取一個32位的數據。這兩種情況下,其對應的值都是以小端字節順序存儲的。

 

字符串

wire type為2意味著其值是一個varint編碼長度,後面接著的是指定字節數的數據。

message Test2 {
  required string b = 2;
}設置b的值為"testing",會得到:

12 07 74 65 73 74 69 6e 67紅色的字節部分是UTF-8編碼的"testing"。key是0x12->tag=2,type2。長度的值是7,隨後的7個字節就是我們的字符串。

 

嵌套的消息

如下的例子中有一個嵌套的消息:

message Test3 {
  required Test1 c = 3;
}編碼後的結果如下,Test1的字段a的值設為150:

 1a 03 08 96 01可以看到,後三個字節和我們前面第一個例子相同(08 96 10),表示數字150,其前面是數字3,嵌套消息被當做字符串來看待(wire type=2)。

 

可選和重復元素

如果你的消息中有repeated元素(沒有[packed=true]選項),編碼後的消息有0個或多個key-value對有相同的tag數字。這些重復的值不需要連續的出現,他們可能與其他的字段交錯出現。元素的順序在解析的時候確定。

如果元素是optional,編碼後的消息可能有或沒有key-value對。

正常情況下,一個編碼的消息擁有的一個optional或required字段的實例不會超過一個。但是,解析器也會處理這種情況。對於數值類型和字符串,如果同樣的值出現了多次,解析器只接受最後的值。對於嵌套的消息字段,解析器合並相同字段的多個實例,就像Message::MergeFrom方法做的一樣——那就是,後面的實例的單個字段會替換掉前面出現的,單個嵌套消息被合並,重復字段被連結。這些規則的效果就是解析串聯出現的兩個編碼的消息產生的結果和單獨解析兩個消息然後合並它們的結果是相同的。示例如下:

MyMessage message;
message.ParseFromString(str1 + str2);等價於:

MyMessage message, message2;
message.ParseFromString(str1);
message2.ParseFromString(str2);
message.MergeFrom(message2);

Packed Repeated字段

版本2.1.0中介紹了packed repeated字段,同時定義了repeated和[packed=true]選項。一個包含0個元素的packed repeated字段不會出現在編碼消息中,除此之外,字段的所有元素被打包到一個key-value對中,用wire type 2標識。每個元素按照自身的類型編碼。

例如,假設你有如下的消息類型:

message Test4 {
  repeated int32 d = 4 [packed=true];
}構造一個Test4,d字段包含的值有3,270,86942。然後,編碼的結果如下:

22        // tag (field number 4, wire type 2)
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942)僅僅repeated的基礎數值類型(varint, 32-bit, 64-bit)才可以聲明"packed"。

 

字段順序

你可以在一個.proto文件中以任何的順序來使用字段數字,當一個消息被序列化的時候,它的已知的字段會按照字段數字順序連續寫入。這個允許解析代碼依賴於字段數字做優化。但是,protocol buffer的解析器應該能夠以任何順序解析字段,因為並不是所有的消息是通過序列化一個對象創建的。

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved