Protobuf的簡單介紹、使用和分析


 

 

Protobuf的簡單介紹、使用和分析

 

一、protobuf是什么?

        protobuf(Google Protocol Buffers)是Google提供一個具有高效的協議數據交換格式工具庫(類似Json),但相比於Json,Protobuf有更高的轉化效率,時間效率和空間效率都是JSON的3-5倍。后面將會有簡單的demo對於這兩種格式的數據轉化效率的對比。但這個庫目前使用還不是太流行,據說谷歌內部很多產品都有使用。

 

二、protobuf有什么?

        Protobuf 提供了C++、java、python語言的支持,提供了windows(proto.exe)和linux平台動態編譯生成proto文件對應的源文件。proto文件定義了協議數據中的實體結構(message ,field)

關鍵字message: 代表了實體結構,由多個消息字段(field)組成。

消息字段(field): 包括數據類型、字段名、字段規則、字段唯一標識、默認值

數據類型:常見的原子類型都支持(在FieldDescriptor::kTypeToName中有定義)

字段規則:(在FieldDescriptor::kLabelToName中定義)

        required:必須初始化字段,如果沒有賦值,在數據序列化時會拋出異常

        optional:可選字段,可以不必初始化。

        repeated:數據可以重復(相當於java 中的Array或List)

        字段唯一標識:序列化和反序列化將會使用到。

默認值:在定義消息字段時可以給出默認值。

 

三、protobuf有什么用?

        Xml、Json是目前常用的數據交換格式,它們直接使用字段名稱維護序列化后類實例中字段與數據之間的映射關系,一般用字符串的形式保存在序列化后的字節流中。消息和消息的定義相對獨立,可讀性較好。但序列化后的數據字節很大,序列化和反序列化的時間較長,數據傳輸效率不高。

        Protobuf和Xml、Json序列化的方式不同,采用了二進制字節的序列化方式,用字段索引和字段類型通過算法計算得到字段之前的關系映射,從而達到更高的時間效率和空間效率,特別適合對數據大小和傳輸速率比較敏感的場合使用。

四、Protobuf在Android上的使用

1、創建proto文件,定義消息的實體結構

2、編譯proto文件生成對應的java文件

3、添加protobuf-java-2.5.0.jar到android工程

4、在android中實現對消息結構的序列化/反序列化  

 

五、Protobuf與json的對比

1、創建product.proto文件

        定義了三個Message(ProductInfo、PhoneInfo、Watch)消息結構

2、消息結構對應的java類(ProductInfo、PhoneInfo、Watch)

 

3、消息結構和java對象賦值

PhoneName:” idol3”

Price:2000

Top:1

 

WatchName:” tcl watch”

Price:1000

Top:1

 

4、JSON字符串

 

{"phone":{"phoneName":"idol3","price":2000,"top":1},"watch":{"watchName":"tcl wtch","top":1,"price":1000}}

 

5、Protobuf轉化后的二進制文件

 

空間效率

Json:107個字節

Protobuf:32個字節

 

時間效率

Json序列化: 1ms ,  反序列化:0ms

Protobuf 序列化: 0ms 反序列化:0ms

 

將public List<Phone> list和repeated PhoneInfo phoneInfoList =3;都賦值為1000個PhoneInfo

 

空間效率

Json:4206個字節

Protobuf:1332個字節

 

時間效率

Json序列化: 4ms ,  反序列化:1ms

Protobuf 序列化: 1ms 反序列化:0ms

六、protobuf的簡單分析

1、優缺點

優點:通過以上的時間效率和空間效率,可以看出protobuf的空間效率是JSON的2-5倍,時間效率要高,對於數據大小敏感,傳輸效率高的模塊可以采用protobuf庫

 

缺點:消息結構可讀性不高,序列化后的字節序列為二進制序列不能簡單的分析有效性;目前使用不廣泛,只支持java,C++和Python;

 

2、數據序列化/反序列化

a、規則:

protobuf把消息結果message也是通過 key-value對來表示。只是其中的key是采取一定的算法計算出來的即通過每個message中每個字段(field index)和字段的數據類型進行運算得來的key = (index<<3)|type;

type類型的對應關系如下:

 

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

 

Value會根據數據類型的不同會有兩種表現形式:

對於各種int,bool,enum類型,value就是Varint

對於string,bytes,message等等類型,value就是length+原始內容編碼

 

Varints是一種緊湊表示數字的方法。它用一個或者多個字節表示一個數字,值越小的數字字節數越少。相對於傳統的用4字節表示int32類型數字,Varints對於小於128的數值都可以用一個字節表示,大於128的數值會用更多的字節來表示,對於很大的數據則需要用5個字節來表示。

 

Varints算法描述: 每一個字節的最高位都是有特殊含義的,如果是1,則表示后續的字節也是該數字的一部分;如果是0,則結束

b、demo生成的的二進制文件反序列化。

第1個字節 (0A)

字段索引(index):         0A = 0001010  0A>>3 = 001 = 1

數據類型(type):           0A = 0001010&111  = 2 (String);

 

第2個字節 (0C)

字符串長度(length):      0E = 12;

字符串:                         0A 05 69 64 6F 6C 33 10 01 18 BD 0F

 

第3個字節 (0A)

因為字符串是來自phoneInfo屬於嵌套類型

字段索引(index):         0A = 0001010  0A>>3 = 001 = 1

數據類型(type):           0A = 0001010&111  = 2 (String);


第4-9個字節(69 64 6F 6C 33)

字符串長度(length):    05 = 5

字符串:                       69 64 6F 6C 33 = idol3

 

第10個字節 (10)

字段索引(index):         10 = 00010000    10A>>3 = 0010 = 2

數據類型(type):           10 = 00010000&111  = 0 (Varints);

 

第11個字節  (01)

Varints:                          01 = 00001字節的最高位為0 整數結束

Value:                            1;

 

第12個字節(18)

字段索引(index):           18 = 00011000    18>> 00011 = 3

數據類型(type):           18 = 00011000&111  = 0 (Varints);

 

第13個字節(D0)

最高位為1,整數計算到下一個字節

 

第14個字節(0F)

最高位為0,整數計算結束

Value:為11111010000 =2000

 

C、反序列化結果

phoneinfo為

phoneName = “idol3”

top = 1

price = 2000;

 

同樣的方法watchInfo為:

watchName = “tcl name”

top = 1

price=2000 

3、時間效率

通過protobuf序列化/反序列化的過程可以得出:protobuf是通過算法生成二進制流,序列化與反序列化不需要解析相應的節點屬性和多余的描述信息,所以序列化和反序列化時間效率較高。

4、空間效率

xml、json是用字段名稱來確定類實例中字段之間的獨立性,所以序列化后的數據多了很多描述信息,增加了序列化后的字節序列的容量。

 

Protobuf的序列化/反序列化過程可以得出:

protobuf是由字段索引(fieldIndex)與數據類型(type)計算(fieldIndex<<3|type)得出的key維護字段之間的映射且只占一個字節,所以相比json與xml文件,protobuf的序列化字節沒有過多的key與描述符信息,所以占用空間要小很多。

七、Protobuf的源碼分析

1、protobuf在java使用的序列化流程

 

java程序調用parserFrom(byte[] data)開始字節序列的反序列,Java程序通過調用編譯生類GenerateMessage中的wirteTo()方法開始將序列化后的字節寫入輸出流中

 

GenerateMessage 繼承AbstractMessage類,序列化最終在AbstractMesssage中完成,序列化的實現過程:

a、遍歷對象中Message結構()

調用AbstractMessage類中的writeTo()方法

 

b、 序列化Message中每一個字段

調用CodeOutputStream類中的writeMessageSetExtension()方法

 

c、 對於Varints  Tag 的序列化流程:

調用CodeOutputStream類中的writeUInt32()方法

調用CodeOutputStream類中的WriteRawVarint32()方法

 

d、 對於非Varints Tag的序列化

調用CodeOutputStream類中的WriteTag()方法

 

 

具體的序列化實現都在CodedOutputStream中完成

 

2、java使用protobuf 的反序列化流程分析

java程序通過調用parserFrom(byte[] data)開始反序列化

 

具體在com.google.protobuf. AbstractParser類中實現

 

 

 

 

 

最后在com.google.protobuf.CodedInputStream類中完成反序列化

3、動態編譯

以windows下用protoc.exe工具實現proto文件編譯為例,protoc.exe是用C++實現。在控制台執行命令:

編譯的流程:

檢查proto的語法規則

將proto的文件中的message結構轉換為GenerateMessage類的子類,並實現Builder接口。

編譯流程

Main.cc中的main()方法

 

Command_line_interface.cc中的Run()方法

 

Import類中Import()

 

在Descriptor中完成message消息的收集和轉化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM