溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java程序中protobuf的基本用法

發(fā)布時(shí)間:2021-08-19 11:22:13 來(lái)源:億速云 閱讀:160 作者:chen 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“java程序中protobuf的基本用法”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“java程序中protobuf的基本用法”吧!

目錄
  • 簡(jiǎn)介

  • 為什么使用protobuf

  • 定義.proto文件

  • 編譯協(xié)議文件

  • 詳解生成的文件

  • Builders 和 Messages

  • 序列化和反序列化

  • 協(xié)議擴(kuò)展


簡(jiǎn)介

Protocol Buffer是google出品的一種對(duì)象序列化的方式,它的體積小傳輸快,深得大家的喜愛。protobuf是一種平臺(tái)無(wú)關(guān)和語(yǔ)言無(wú)關(guān)的協(xié)議,通過(guò)protobuf的定義文件,可以輕松的將其轉(zhuǎn)換成多種語(yǔ)言的實(shí)現(xiàn),非常方便。

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

為什么使用protobuf

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

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

  1. 使用JDK自帶的對(duì)象序列化,但是JDK自帶的序列化本身存在一些問(wèn)題,并且這種序列化手段只適合在java程序之間進(jìn)行傳輸,如果是非java程序,比如PHP或者GO,那么序列化就不通用了。

  2. 你還可以自定義序列化協(xié)議,這種方式的靈活程度比較高,但是不夠通用,并且實(shí)現(xiàn)起來(lái)也比較復(fù)雜,很可能出現(xiàn)意想不到的問(wèn)題。

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

所以我們需要一種新的序列化的方法,這就是protobuf,它是一種靈活、高效、自動(dòng)化的解決方案。

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

定義.proto文件

.proto文件中定義的是你將要序列化的消息對(duì)象。我們來(lái)一個(gè)最基本的student.proto文件,這個(gè)文件定義了student這個(gè)對(duì)象中最基本的屬性。

先看一個(gè)比較簡(jiǎn)單的.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é)議,默認(rèn)情況下是proto2,因?yàn)槟壳白钚碌膮f(xié)議是proto3,所以這里我們使用proto3作為例子。

然后我們定義了所在的package,這個(gè)package是指編譯的時(shí)候生成文件的包。這是一個(gè)命名空間,雖然我們?cè)诤竺娑x了java_package,但是為了和非java語(yǔ)言中的協(xié)議相沖突,所以定義package還是非常有必要的。

然后是三個(gè)專門給java程序使用的option。java_multiple_files, java_package, 和 java_outer_classname.

其中java_multiple_files指編譯過(guò)后java文件的個(gè)數(shù),如果是true,那么將會(huì)一個(gè)java對(duì)象一個(gè)類,如果是false,那么定義的java對(duì)象將會(huì)被包含在同一個(gè)文件中。

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

java_outer_classname選項(xiàng)定義將表示此文件的包裝類的類名。 如果沒(méi)有給java_outer_classname賦值,它將通過(guò)將文件名轉(zhuǎn)換為大寫駝峰來(lái)生成。 例如,默認(rèn)情況下,“student.proto”將使用”Student”作為包裝類名稱。

接下來(lái)的部分是消息的定義,對(duì)于簡(jiǎn)單類型來(lái)說(shuō)可以使用bool, int32, float, double, 和 string來(lái)定義字段的類型。

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

上面我們?yōu)槊總€(gè)屬性值分配了ID,這個(gè)ID是二進(jìn)制編碼中使用的唯一“標(biāo)簽”。因?yàn)樵趐rotobuf中標(biāo)記數(shù)字1-15比16以上的標(biāo)記數(shù)字占用的字節(jié)空間要更少,因此作為一種優(yōu)化,通常將1-15這些標(biāo)記用于常用或重復(fù)的元素,而將標(biāo)記16和更高的標(biāo)記用于不太常用的可選元素。

然后再來(lái)看看字段的修飾符,有三個(gè)修飾符分別是optional,repeated和required。

optional表示該字段是可選的,可以設(shè)置也可以不設(shè)置,如果沒(méi)有設(shè)置,則會(huì)使使用默認(rèn)值,對(duì)于簡(jiǎn)單類型來(lái)說(shuō),我們可以自定義默認(rèn)值,如果不自定義,就會(huì)使用系統(tǒng)的默認(rèn)值。對(duì)于系統(tǒng)的默認(rèn)值來(lái)說(shuō),數(shù)字為0,字符串為空字符串,布爾值為false。

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

required表示該字段是必須的,如果該字段沒(méi)有值,那么該字段將會(huì)被認(rèn)為是沒(méi)有初始化,嘗試構(gòu)建未初始化的消息將拋出 RuntimeException,解析未初始化的消息將拋出 IOException。

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

編譯協(xié)議文件

定義好proto文件之后,就可以使用protoc命令對(duì)其進(jìn)行編譯了。

protoc是protobuf提供的編譯器,一般情況下,可以從github的release庫(kù)中直接下載即可。如果你不想直接下載,或者官方提供的庫(kù)中并沒(méi)有你需要的版本,則可以使用源代碼直接進(jìn)行編譯。

protoc的使用的命令如下:

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

如果編譯proto3,則需要添加–experimental_allow_proto3_optional選項(xiàng)。

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

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

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

詳解生成的文件

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

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

可以看到他們實(shí)現(xiàn)的接口都是一樣的,表示他們可能提供了相同的功能。實(shí)際上Builder是對(duì)消息的一個(gè)封裝器,所有對(duì)Student的操作都可以由Builder來(lái)完成。

對(duì)于Student中的字段來(lái)說(shuō),Student類只有這些字段的get方法,而Builder中同時(shí)有g(shù)et和set方法。

對(duì)于Student來(lái)說(shuō),對(duì)于字段的方法有:

// 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);

對(duì)于Builder來(lái)說(shuō),每個(gè)屬性多了兩個(gè)方法:

// 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();

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

我們還定義了一個(gè)枚舉類PhoneType:

  public enum PhoneType
      implements com.google.protobuf.ProtocolMessageEnum

這個(gè)類的實(shí)現(xiàn)和普通的枚舉類沒(méi)太大區(qū)別。

Builders 和 Messages

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

每次調(diào)用Builder的方法都會(huì)返回一個(gè)新的Builder,當(dāng)然這個(gè)返回的Builder和原來(lái)的Builder是同一個(gè),返回Builder只是為了方便進(jìn)行代碼的連寫。

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

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()檢測(cè)是否所有必須的字段都設(shè)置完畢。toString()將對(duì)象轉(zhuǎn)換成為字符串。使用它的Builder還可以調(diào)用clear()用來(lái)清除已設(shè)置的狀態(tài),mergeFrom(Message other)用來(lái)對(duì)對(duì)象進(jìn)行合并。

序列化和反序列化

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

  • byte[] toByteArray();: 序列化消息并返回一個(gè)包含其原始字節(jié)的字節(jié)數(shù)組。

  • static Person parseFrom(byte[] data);: 從給定的字節(jié)數(shù)組中解析一條消息。

  • void writeTo(OutputStream output);: 序列化消息并將其寫入 OutputStream.

  • static Person parseFrom(InputStream input);: 從一個(gè)消息中讀取并解析消息 InputStream.

通過(guò)使用上面的方法,可以很方便的將對(duì)象進(jìn)行序列化和反序列化。

協(xié)議擴(kuò)展

我們?cè)诙x好proto之后,假如后續(xù)還希望對(duì)其進(jìn)行修改,那么我們希望新的協(xié)議對(duì)歷史數(shù)據(jù)是兼容的。那么我們需要考慮下面幾點(diǎn):

  1. 不能更改現(xiàn)有字段的ID編號(hào)。

  2. 不能添加和刪除任何必填字段。

  3. 可以 刪除可選或重復(fù)的字段。

  4. 可以 添加新的可選字段或重復(fù)字段,但您必須使用新的ID編號(hào)。

到此,相信大家對(duì)“java程序中protobuf的基本用法”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI