在Java程序中使用Protobuf

Protocol Buffer是google出品的一種對象序列化的方式质蕉,它的體積小傳輸快持痰,深得大家的喜愛。protobuf是一種平臺無關(guān)和語言無關(guān)的協(xié)議五续,通過protobuf的定義文件弟断,可以輕松的將其轉(zhuǎn)換成多種語言的實現(xiàn)咏花,非常方便。

今天將會給大家介紹一下阀趴,protobuf的基本使用和同java結(jié)合的具體案例昏翰。

為什么使用protobuf

我們知道數(shù)據(jù)在網(wǎng)絡(luò)傳輸中是以二進制進行的,一般我們使用字節(jié)byte來表示刘急, 一個byte是8bits棚菊,如果要在網(wǎng)絡(luò)上中傳輸對象,一般需要將對象序列化叔汁,序列化的目的就是將對象轉(zhuǎn)換成byte數(shù)組在網(wǎng)絡(luò)中傳輸统求,當(dāng)接收方接收到byte數(shù)組之后,再對byte數(shù)組進行反序列化据块,最終轉(zhuǎn)換成java中的對象码邻。

那么將java對象序列化可能會有如下幾種方法:

  • 使用JDK自帶的對象序列化,但是JDK自帶的序列化本身存在一些問題瑰钮,并且這種序列化手段只適合在java程序之間進行傳輸冒滩,如果是非java程序微驶,比如PHP或者GO浪谴,那么序列化就不通用了。

  • 你還可以自定義序列化協(xié)議因苹,這種方式的靈活程度比較高苟耻,但是不夠通用,并且實現(xiàn)起來也比較復(fù)雜扶檐,很可能出現(xiàn)意想不到的問題凶杖。

  • 將數(shù)據(jù)轉(zhuǎn)換成為XML或者JSON進行傳輸。XML和JSON的好處在于他們都有可以區(qū)分對象的起始符號款筑,通過判斷這些符號的位置就可以讀取到完整的對象智蝠。但是不管是XML還是JSON的缺點都是轉(zhuǎn)換成的數(shù)據(jù)比較大腾么。在反序列化的時候?qū)Y源的消耗也比較多。

所以我們需要一種新的序列化的方法杈湾,這就是protobuf解虱,它是一種靈活、高效漆撞、自動化的解決方案殴泰。

通過編寫一個.proto的數(shù)據(jù)結(jié)構(gòu)定義文件,然后調(diào)用protobuf的編譯器浮驳,就會生成對應(yīng)的類悍汛,該類以高效的二進制格式實現(xiàn)protobuf數(shù)據(jù)的自動編碼和解析。 生成的類為定義文件中的數(shù)據(jù)字段提供了getter和setter方法至会,并提供了讀寫的處理細節(jié)离咐。 重要的是,protobuf可以向前兼容奋献,也就是說老的二進制代碼也可以使用最新的協(xié)議進行讀取健霹。

定義.proto文件

.proto文件中定義的是你將要序列化的消息對象。我們來一個最基本的student.proto文件瓶蚂,這個文件定義了student這個對象中最基本的屬性糖埋。

先看一個比較簡單的.proto文件:

syntax = "proto3";

package com.flydean;

option java_multiple_files = true;
option java_package = "com.flydean.tutorial.protos";
option java_outer_classname = "StudentListProtos";

message Student {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
  }

  message PhoneNumber {
    optional string number = 1;
    optional PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message StudentList {
  repeated Student student = 1;
}

第一行定義的是protobuf中使用的syntax協(xié)議,默認情況下是proto2窃这,因為目前最新的協(xié)議是proto3瞳别,所以這里我們使用proto3作為例子。

然后我們定義了所在的package杭攻,這個package是指編譯的時候生成文件的包祟敛。這是一個命名空間,雖然我們在后面定義了java_package兆解,但是為了和非java語言中的協(xié)議相沖突馆铁,所以定義package還是非常有必要的。

然后是三個專門給java程序使用的option锅睛。java_multiple_files, java_package埠巨, 和 java_outer_classname.

其中java_multiple_files指編譯過后java文件的個數(shù),如果是true现拒,那么將會一個java對象一個類辣垒,如果是false,那么定義的java對象將會被包含在同一個文件中印蔬。

java_package指定生成的類應(yīng)該使用的Java包名稱勋桶。 如果沒有明確的指定,則會使用之前定義的package的值。

java_outer_classname選項定義將表示此文件的包裝類的類名例驹。 如果沒有給java_outer_classname賦值捐韩,它將通過將文件名轉(zhuǎn)換為大寫駝峰來生成。 例如鹃锈,默認情況下奥帘,“student.proto”將使用”Student”作為包裝類名稱。

接下來的部分是消息的定義仪召,對于簡單類型來說可以使用bool, int32, float, double寨蹋, 和 string來定義字段的類型。

上例中我們還使用了復(fù)雜的組合屬性扔茅,和嵌套類型已旧。還定義了一個枚舉類。

上面我們?yōu)槊總€屬性值分配了ID召娜,這個ID是二進制編碼中使用的唯一“標簽”运褪。因為在protobuf中標記數(shù)字1-15比16以上的標記數(shù)字占用的字節(jié)空間要更少,因此作為一種優(yōu)化玖瘸,通常將1-15這些標記用于常用或重復(fù)的元素秸讹,而將標記16和更高的標記用于不太常用的可選元素。

然后再來看看字段的修飾符雅倒,有三個修飾符分別是optional璃诀,repeated和required。

optional表示該字段是可選的蔑匣,可以設(shè)置也可以不設(shè)置劣欢,如果沒有設(shè)置,則會使使用默認值裁良,對于簡單類型來說凿将,我們可以自定義默認值,如果不自定義价脾,就會使用系統(tǒng)的默認值牧抵。對于系統(tǒng)的默認值來說,數(shù)字為0侨把,字符串為空字符串犀变,布爾值為false。

repeated表示該字段是可以重復(fù)的座硕,這種重復(fù)實際上就是一種數(shù)組的結(jié)構(gòu)弛作。

required表示該字段是必須的涕蜂,如果該字段沒有值华匾,那么該字段將會被認為是沒有初始化,嘗試構(gòu)建未初始化的消息將拋出 RuntimeException,解析未初始化的消息將拋出 IOException蜘拉。

注意萨西,在Proto3中不支持required字段。

編譯協(xié)議文件

定義好proto文件之后旭旭,就可以使用protoc命令對其進行編譯了谎脯。

protoc是protobuf提供的編譯器,一般情況下持寄,可以從github的release庫中直接下載即可源梭。如果你不想直接下載,或者官方提供的庫中并沒有你需要的版本稍味,則可以使用源代碼直接進行編譯废麻。

protoc的使用的命令如下:

protoc --experimental_allow_proto3_optional -I=SRC_DIR --java_out=DST_DIR $SRC_DIR/student.proto

如果編譯proto3,則需要添加–experimental_allow_proto3_optional選項。

我們運行一下上面的代碼模庐。會發(fā)現(xiàn)在com.flydean.tutorial.protos包里面生成了5個文件烛愧。分別是:

Student.java              
StudentList.java          
StudentListOrBuilder.java 
StudentListProtos.java    
StudentOrBuilder.java

其中StudentListOrBuilder和StudentOrBuilder是兩個接口,Student和StudentList是這兩個類的實現(xiàn)掂碱。

詳解生成的文件

在proto文件中怜姿,我們主要定義了兩個類Student和StudentList, 他們中定義了一個內(nèi)部類Builder,以Student為例疼燥,看下這個兩個類的定義:

public final class Student extends
    com.google.protobuf.GeneratedMessageV3 implements
    StudentOrBuilder

  public static final class Builder extends
      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
      com.flydean.tutorial.protos.StudentOrBuilder

可以看到他們實現(xiàn)的接口都是一樣的沧卢,表示他們可能提供了相同的功能。實際上Builder是對消息的一個封裝器醉者,所有對Student的操作都可以由Builder來完成搏恤。

對于Student中的字段來說,Student類只有這些字段的get方法湃交,而Builder中同時有g(shù)et和set方法熟空。

對于Student來說,對于字段的方法有:

// required string name = 1;
public boolean hasName();
public String getName();

// required int32 id = 2;
public boolean hasId();
public int getId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();

// repeated .tutorial.Person.PhoneNumber phones = 4;
public List<PhoneNumber> getPhonesList();
public int getPhonesCount();
public PhoneNumber getPhones(int index);

對于Builder來說搞莺,每個屬性多了兩個方法:

// required string name = 1;
public boolean hasName();
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName();

// required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail();

// repeated .tutorial.Person.PhoneNumber phones = 4;
public List<PhoneNumber> getPhonesList();
public int getPhonesCount();
public PhoneNumber getPhones(int index);
public Builder setPhones(int index, PhoneNumber value);
public Builder addPhones(PhoneNumber value);
public Builder addAllPhones(Iterable<PhoneNumber> value);
public Builder clearPhones();

多出的兩個方法是set和clear方法息罗。clear是清空字段的內(nèi)容,讓其變回初始狀態(tài)才沧。

我們還定義了一個枚舉類PhoneType:

public enum PhoneType
      implements com.google.protobuf.ProtocolMessageEnum

這個類的實現(xiàn)和普通的枚舉類沒太大區(qū)別迈喉。

Builders 和 Messages

如上一節(jié)所示,Message對應(yīng)的類只有g(shù)et和has方法温圆,所以它是不可以變的挨摸,消息對象一旦被構(gòu)造,就不能被修改岁歉。要構(gòu)建消息得运,必須首先構(gòu)建一個構(gòu)建器,將要設(shè)置的任何字段設(shè)置為你選擇的值,然后調(diào)用構(gòu)建器的 build()方法熔掺。

每次調(diào)用Builder的方法都會返回一個新的Builder饱搏,當(dāng)然這個返回的Builder和原來的Builder是同一個,返回Builder只是為了方便進行代碼的連寫置逻。

下面的代碼是如何創(chuàng)建一個Student實例:

Student xiaoming =
                Student.newBuilder()
                        .setId(1234)
                        .setName("小明")
                        .setEmail("flydean@163.com")
                        .addPhones(
                                Student.PhoneNumber.newBuilder()
                                        .setNumber("010-1234567")
                                        .setType(Student.PhoneType.HOME))
                        .build();

Student中提供了一些常用的方法推沸,如isInitialized()檢測是否所有必須的字段都設(shè)置完畢。toString()將對象轉(zhuǎn)換成為字符串券坞。使用它的Builder還可以調(diào)用clear()用來清除已設(shè)置的狀態(tài)鬓催,mergeFrom(Message other)用來對對象進行合并。

序列化和反序列化

生成的對象中提供了序列化和反序列化方法恨锚,我們只需要在需要的時候?qū)ζ溥M行調(diào)用即可:

  • byte[] toByteArray();: 序列化消息并返回一個包含其原始字節(jié)的字節(jié)數(shù)組深浮。
  • static Person parseFrom(byte[] data);: 從給定的字節(jié)數(shù)組中解析一條消息。
  • void writeTo(OutputStream output);: 序列化消息并將其寫入 OutputStream.
  • static Person parseFrom(InputStream input);: 從一個消息中讀取并解析消息 InputStream.
    通過使用上面的方法眠冈,可以很方便的將對象進行序列化和反序列化飞苇。

協(xié)議擴展

我們在定義好proto之后,假如后續(xù)還希望對其進行修改蜗顽,那么我們希望新的協(xié)議對歷史數(shù)據(jù)是兼容的布卡。那么我們需要考慮下面幾點:

1.不能更改現(xiàn)有字段的ID編號。
2.不能添加和刪除任何必填字段雇盖。
3.可以 刪除可選或重復(fù)的字段忿等。
4.可以 添加新的可選字段或重復(fù)字段,但您必須使用新的ID編號崔挖。

好了贸街,protocol buf的基本用法就介紹到這里,覺得有幫助的小伙伴記得點贊收藏哦~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狸相,一起剝皮案震驚了整個濱河市薛匪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脓鹃,老刑警劉巖逸尖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瘸右,居然都是意外死亡娇跟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門太颤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苞俘,“玉大人,你說我怎么就攤上這事龄章〕砸ィ” “怎么了乞封?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長基协。 經(jīng)常有香客問我,道長菇用,這世上最難降的妖魔是什么澜驮? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮惋鸥,結(jié)果婚禮上杂穷,老公的妹妹穿的比我還像新娘。我一直安慰自己卦绣,他們只是感情好耐量,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滤港,像睡著了一般廊蜒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溅漾,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天山叮,我揣著相機與錄音,去河邊找鬼添履。 笑死屁倔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的暮胧。 我是一名探鬼主播锐借,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼往衷!你這毒婦竟也來了钞翔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤席舍,失蹤者是張志新(化名)和其女友劉穎嗅战,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俺亮,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡驮捍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了脚曾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片东且。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖本讥,靈堂內(nèi)的尸體忽然破棺而出珊泳,到底是詐尸還是另有隱情鲁冯,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布色查,位于F島的核電站薯演,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秧了。R本人自食惡果不足惜跨扮,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望验毡。 院中可真熱鬧衡创,春花似錦、人聲如沸晶通。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狮辽。三九已至一也,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喉脖,已是汗流浹背塘秦。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留动看,地道東北人尊剔。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像菱皆,于是被迫代替她去往敵國和親须误。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容