protocolbuffer(以下簡(jiǎn)稱(chēng)PB)是google 的一種數(shù)據(jù)交換的格式,它獨(dú)立于語(yǔ)言,獨(dú)立于平臺(tái)。google 提供了多種語(yǔ)言的實(shí)現(xiàn):java、c#、c++、go 和 python,每一種實(shí)現(xiàn)都包含了相應(yīng)語(yǔ)言的編譯器以及庫(kù)文件。由于它是一種二進(jìn)制的格式,比使用 xml 進(jìn)行數(shù)據(jù)交換快許多??梢园阉糜诜植际綉?yīng)用之間的數(shù)據(jù)通信或者異構(gòu)環(huán)境下的數(shù)據(jù)交換。作為一種效率和兼容性都很優(yōu)秀的二進(jìn)制數(shù)據(jù)傳輸格式,可以用于諸如網(wǎng)絡(luò)傳輸、配置文件、數(shù)據(jù)存儲(chǔ)等諸多領(lǐng)域。
1.ProtoBuf協(xié)議說(shuō)明
proto文件定義了協(xié)議數(shù)據(jù)中的實(shí)體結(jié)構(gòu)(message ,field)
- 關(guān)鍵字message: 代表了實(shí)體結(jié)構(gòu),由多個(gè)消息字段(field)組成。
- 消息字段(field): 包括數(shù)據(jù)類(lèi)型、字段名、字段規(guī)則、字段唯一標(biāo)識(shí)、默認(rèn)值
- 數(shù)據(jù)類(lèi)型:如下圖所示
- 字段規(guī)則:
required:必須初始化字段,如果沒(méi)有賦值,在數(shù)據(jù)序列化時(shí)會(huì)拋出異常
optional:可選字段,可以不必初始化。
repeated:數(shù)據(jù)可以重復(fù)(相當(dāng)于java 中的Array或List)
字段唯一標(biāo)識(shí):序列化和反序列化將會(huì)使用到。
- 默認(rèn)值:在定義消息字段時(shí)可以給出默認(rèn)值。

2.ProtoBuf的使用流程
1.定義.proto文件
首先我們需要編寫(xiě)一個(gè) proto 文件,定義我們程序中需要處理的結(jié)構(gòu)化數(shù)據(jù),在 protobuf 的術(shù)語(yǔ)中,結(jié)構(gòu)化數(shù)據(jù)被稱(chēng)為 Message。proto 文件非常類(lèi)似 java 或者 C 語(yǔ)言的數(shù)據(jù)定義。下面代碼顯示了例子應(yīng)用中的 proto 文件內(nèi)容:
package lm;
message helloworld
{
required int32 id = 1; // ID
required string str = 2; // str
optional int32 opt = 3; //optional field
}
一個(gè)比較好的習(xí)慣是認(rèn)真對(duì)待 proto 文件的文件名。比如將命名規(guī)則定于如下:
packageName.MessageName.proto
在上例中,package 名字叫做 lm,定義了一個(gè)消息 helloworld,該消息有三個(gè)成員,類(lèi)型為 int32 的 id,另一個(gè)為類(lèi)型為 string 的成員 str。opt 是一個(gè)可選的成員,即消息中可以不包含該成員。
2.編譯.proto文件
寫(xiě)好 proto 文件之后就可以用 Protobuf 編譯器將該文件編譯成目標(biāo)語(yǔ)言了??梢愿鶕?jù)不同的語(yǔ)言來(lái)選擇不同的編譯方式
在項(xiàng)目中我使用到的是分別是JavaScript和Java,前者是客戶(hù)端腳本語(yǔ)言,后者服務(wù)端語(yǔ)言。將proto文件編譯生成java文件,這里有一個(gè)很好的案例:《Protobuf協(xié)議的Java應(yīng)用例子》,當(dāng)然在項(xiàng)目中一般是不會(huì)這么干的,
3.序列化和反序列化
public class Test {
public static void main(String[] args) throws IOException {
//模擬將對(duì)象轉(zhuǎn)成byte[],方便傳輸
PersonEntity.Person.Builder builder = PersonEntity.Person.newBuilder();
builder.setId(1);
builder.setName("ant");
builder.setEmail("ghb@soecode.com");
PersonEntity.Person person = builder.build();
System.out.println("before :"+ person.toString());
System.out.println("===========Person Byte==========");
for(byte b : person.toByteArray()){
System.out.print(b);
}
System.out.println();
System.out.println(person.toByteString());
System.out.println("================================");
//模擬接收Byte[],反序列化成Person類(lèi)
byte[] byteArray =person.toByteArray();
Person p2 = Person.parseFrom(byteArray);
System.out.println("after :" +p2.toString());
}
}

從上面可以總結(jié)出protobuf的使用過(guò)程可以分為以下三個(gè),準(zhǔn)備好數(shù)據(jù),通過(guò)build()方法來(lái)組裝成protobuf包,然后通過(guò)toByteArray()來(lái)將protobuf轉(zhuǎn)換成二進(jìn)制序列流文件(序列化)。
反序列化的過(guò)程剛好與之相反,接收到的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制數(shù)組byte[],然后調(diào)用protobuf的parseFrom()方法即可實(shí)現(xiàn)反序列化。

3.protoBuf數(shù)據(jù)協(xié)議的優(yōu)勢(shì)
- 平臺(tái)無(wú)關(guān),語(yǔ)言無(wú)關(guān),可擴(kuò)展;
- 提供了友好的動(dòng)態(tài)庫(kù),使用簡(jiǎn)單;
- 解析速度快,比對(duì)應(yīng)的XML快約20-100倍;
- 序列化數(shù)據(jù)非常簡(jiǎn)潔、緊湊,與XML相比,其序列化之后的數(shù)據(jù)量約為1/3到1/10。
說(shuō)明:
數(shù)據(jù)量小是因?yàn)?,Protobuf 序列化后所生成的二進(jìn)制消息非常緊湊,這得益于 Protobuf 采用的非常巧妙的little-endian編碼方法。
轉(zhuǎn)換速度快。首先我們來(lái)了解一下 XML 的封解包過(guò)程。XML 需要從文件中讀取出字符串,再轉(zhuǎn)換為 XML 文檔對(duì)象結(jié)構(gòu)模型。之后,再?gòu)?XML 文檔對(duì)象結(jié)構(gòu)模型中讀取指定節(jié)點(diǎn)的字符串,最后再將這個(gè)字符串轉(zhuǎn)換成指定類(lèi)型的變量。這個(gè)過(guò)程非常復(fù)雜,其中將 XML 文件轉(zhuǎn)換為文檔對(duì)象結(jié)構(gòu)模型的過(guò)程通常需要完成詞法文法分析等大量消耗 CPU 的復(fù)雜計(jì)算。
反觀(guān) Protobuf,它只需要簡(jiǎn)單地將一個(gè)二進(jìn)制序列,按照指定的格式讀取到 C++ 對(duì)應(yīng)的結(jié)構(gòu)類(lèi)型中就可以了。從上一節(jié)的描述可以看到消息的 decoding 過(guò)程也可以通過(guò)幾個(gè)位移操作組成的表達(dá)式計(jì)算即可完成。速度非???。