日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

在java程序中使用protobuf

發布時間:2024/2/28 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在java程序中使用protobuf 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 簡介
  • 為什么使用protobuf
  • 定義.proto文件
  • 編譯協議文件
  • 詳解生成的文件
  • Builders 和 Messages
  • 序列化和反序列化
  • 協議擴展
  • 總結

簡介

Protocol Buffer是google出品的一種對象序列化的方式,它的體積小傳輸快,深得大家的喜愛。protobuf是一種平臺無關和語言無關的協議,通過protobuf的定義文件,可以輕松的將其轉換成多種語言的實現,非常方便。

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

為什么使用protobuf

我們知道數據在網絡傳輸中是以二進制進行的,一般我們使用字節byte來表示, 一個byte是8bits,如果要在網絡上中傳輸對象,一般需要將對象序列化,序列化的目的就是將對象轉換成byte數組在網絡中傳輸,當接收方接收到byte數組之后,再對byte數組進行反序列化,最終轉換成java中的對象。

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

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

  • 你還可以自定義序列化協議,這種方式的靈活程度比較高,但是不夠通用,并且實現起來也比較復雜,很可能出現意想不到的問題。

  • 將數據轉換成為XML或者JSON進行傳輸。XML和JSON的好處在于他們都有可以區分對象的起始符號,通過判斷這些符號的位置就可以讀取到完整的對象。但是不管是XML還是JSON的缺點都是轉換成的數據比較大。在反序列化的時候對資源的消耗也比較多。

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

    通過編寫一個.proto的數據結構定義文件,然后調用protobuf的編譯器,就會生成對應的類,該類以高效的二進制格式實現protobuf數據的自動編碼和解析。 生成的類為定義文件中的數據字段提供了getter和setter方法,并提供了讀寫的處理細節。 重要的是,protobuf可以向前兼容,也就是說老的二進制代碼也可以使用最新的協議進行讀取。

    定義.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協議,默認情況下是proto2,因為目前最新的協議是proto3,所以這里我們使用proto3作為例子。

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

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

    其中java_multiple_files指編譯過后java文件的個數,如果是true,那么將會一個java對象一個類,如果是false,那么定義的java對象將會被包含在同一個文件中。

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

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

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

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

    上面我們為每個屬性值分配了ID,這個ID是二進制編碼中使用的唯一“標簽”。因為在protobuf中標記數字1-15比16以上的標記數字占用的字節空間要更少,因此作為一種優化,通常將1-15這些標記用于常用或重復的元素,而將標記16和更高的標記用于不太常用的可選元素。

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

    optional表示該字段是可選的,可以設置也可以不設置,如果沒有設置,則會使使用默認值,對于簡單類型來說,我們可以自定義默認值,如果不自定義,就會使用系統的默認值。對于系統的默認值來說,數字為0,字符串為空字符串,布爾值為false。

    repeated表示該字段是可以重復的,這種重復實際上就是一種數組的結構。

    required表示該字段是必須的,如果該字段沒有值,那么該字段將會被認為是沒有初始化,嘗試構建未初始化的消息將拋出 RuntimeException,解析未初始化的消息將拋出 IOException。

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

    編譯協議文件

    定義好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選項。

    我們運行一下上面的代碼。會發現在com.flydean.tutorial.protos包里面生成了5個文件。分別是:

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

    其中StudentListOrBuilder和StudentOrBuilder是兩個接口,Student和StudentList是這兩個類的實現。

    詳解生成的文件

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

    public final class Student extendscom.google.protobuf.GeneratedMessageV3 implementsStudentOrBuilderpublic static final class Builder extendscom.google.protobuf.GeneratedMessageV3.Builder<Builder> implementscom.flydean.tutorial.protos.StudentOrBuilder

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

    對于Student中的字段來說,Student類只有這些字段的get方法,而Builder中同時有get和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是清空字段的內容,讓其變回初始狀態。

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

    public enum PhoneTypeimplements com.google.protobuf.ProtocolMessageEnum

    這個類的實現和普通的枚舉類沒太大區別。

    Builders 和 Messages

    如上一節所示,Message對應的類只有get和has方法,所以它是不可以變的,消息對象一旦被構造,就不能被修改。要構建消息,必須首先構建一個構建器,將要設置的任何字段設置為你選擇的值,然后調用構建器的 build()方法。

    每次調用Builder的方法都會返回一個新的Builder,當然這個返回的Builder和原來的Builder是同一個,返回Builder只是為了方便進行代碼的連寫。

    下面的代碼是如何創建一個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()檢測是否所有必須的字段都設置完畢。toString()將對象轉換成為字符串。使用它的Builder還可以調用clear()用來清除已設置的狀態,mergeFrom(Message other)用來對對象進行合并。

    序列化和反序列化

    生成的對象中提供了序列化和反序列化方法,我們只需要在需要的時候對其進行調用即可:

    • byte[] toByteArray();: 序列化消息并返回一個包含其原始字節的字節數組。
    • static Person parseFrom(byte[] data);: 從給定的字節數組中解析一條消息。
    • void writeTo(OutputStream output);: 序列化消息并將其寫入 OutputStream.
    • static Person parseFrom(InputStream input);: 從一個消息中讀取并解析消息 InputStream.

    通過使用上面的方法,可以很方便的將對象進行序列化和反序列化。

    協議擴展

    我們在定義好proto之后,假如后續還希望對其進行修改,那么我們希望新的協議對歷史數據是兼容的。那么我們需要考慮下面幾點:

  • 不能更改現有字段的ID編號。
  • 不能添加和刪除任何必填字段。
  • 可以 刪除可選或重復的字段。
  • 可以 添加新的可選字段或重復字段,但您必須使用新的ID編號。
  • 總結

    好了,protocol buf的基本用法就介紹到這里,下一篇文章我們會更加詳細的介紹proto協議的具體內容,敬請期待。

    本文的例子可以參考:learn-java-base-9-to-20

    本文已收錄于 http://www.flydean.com/01-protocolbuf-guide/

    最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

    歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!

    總結

    以上是生活随笔為你收集整理的在java程序中使用protobuf的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。