ProtoBuf在java中的實際應用

在實際的應用之前,我們再了解以下protobuf。
通過比較它與其他數據格式進行比較,可以使我們更好的認識它的應用場景。下面與XML,JSON進行一個簡單的比較。

  • JSON:一般在web項目中廣泛使用,主要是由于瀏覽器的支持非常好,內部構建了與多函數來支持JSON。具有可讀性。
  • XML:在WebService中廣泛使用,但是過于冗余(畢竟是通過標簽進行標識)。也具有可讀性。
  • ProtoBuf:適合高性能,對響應速度有要求的數據傳輸場景。由于是二進制數據格式需要編碼和解碼。不具有可讀性。在官網上描述ProtoBuf比XML小3到10倍,快20到100倍。

結論: 在一個需要大量的數據傳輸的場景中,如果數據量很大,那么選擇protobuf可以明顯的減少數據量,減少網絡IO,從而減少網絡傳輸所消耗的時間。


在上面我們對ProtoBuf與其他數據格式做了比較,只簡單的了解到它的小和快,可這是如何實現的呢?那么我們在下面對ProtoBuf存儲原理做一個簡要的介紹。以下內容轉載自博文-google protobuf存儲原理及一些底層api應用

核心是Google 提出了“Base 128 Varints”編碼,這是一種變字節長度的編碼,官方描述為:varints是用一個或多個字節序列化整形的一種方法。

序列化方式
protobuf 把 message通過一系列key_value對來表示。
Key 的算法為:(field_number << 3)| wired_type
這里field_number 就是具體的索引,wired_type的值按下表查詢。

wired_type .proto類型
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
5 32-bit fixed32, sfixed32, float

對于int,bool,enum類型,value就是Varint。
而對于string,bytes,message等等類型,value是長度+原始內容編碼。

  • 舉例int類型存儲(Varint存儲原理)
    存儲一個int32 類型的數字,通常是4個字節。但是Varints最少只需要一個字節就可以了。
    Varints規定小于128的數字都可以用一個字節來表示,比如10, 它就會用一個字節 0000 1010 來存儲。
    對于大于128的數字,則用更多個字節存儲。
    以150舉例:protobuf的存儲字節是 1001 0110 0000 0001。
    為什么會這樣標識呢?首先我們了解一個字節共8位,表示的數字是255,但是Varints只用一個字節表示小于128的數字,換句話說,就是Varints只用了8位中的7位來表示數字,而還有一位被用來干嘛了呢?
    Varints在官方規定中表示,每個字節的最高位是有特殊含義,當最高位為1的時候,代表后續的字節也是該數字的一部分,后續為0的時候,則表示結束。
    比如過150,二進制表示為 1001 0110。
    先取后七位 001 0110, 作為第一個字節的內容。
    再取余下的位,補0湊齊7位,就是000 0001。
    對于intel機器,是小端字節序,低字節位于地址低的。0010110 是低字節地址,因此排在前面,因為后面的也是數字的一部分,所以高位補1,也就成了10010110。 同樣的,高字節000 0001,排在后面,并且它后面沒有后續字節了,所以補0,也就成了 0000 0001。
    因此150 在protobuf中的表示方式為 1001 0110 0000 0001。
  • 舉例string類型存儲
message Test {
    required string desc = 2;
}

假如把 a 設置為 “testing”的話, 那么序列化后的就是
12 07 74 65 73 74 69 64 67
其中12是key。剩下的是value。
怎么算的呢?先看12, 這里的12,是個16進制數字,其二進制位表示為 0001 0010。
0010 就是類型string的對應的Type值,根據上表,也就是2。
field_number 是 2,也就是0010,左移三位,就成了0001 0000。
按照key的計算公式,和Type值取并后就變成了 0001 0010,即12。
Value是長度加原始內容編碼。
07就是長度, 代表string總長7個字節。 后面7個數字一次代表每個字母所對應的16進制表示。


下面通過java配合protobuf進行一些簡單的應用,以下操作類請參考初識ProtoBuf,添加依賴或者jar包,此處我是通過maven項目進行演示。

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.6.1</version>
</dependency>

java代碼展示如下:

//創建對象,及屬性賦值
PersonProto.Person.Builder builder = PersonProto.Person.newBuilder();

builder.setName("Mrzhang")
        .setAge(18)
        .setSex(true)
        .setBirthday(System.currentTimeMillis())
        .setAddress("軍事基地")
        .addCars(0, PersonProto.Car.newBuilder().setName("蘭博基尼").setColor("Red").build())
        .putOther("描述", "暫無");
PersonProto.Person person = builder.build();

//序列化(通過protobuf生成的java類的內部方法進行序列化)
byte[] bytes = person.toByteArray();

//反序列化(通過protobuf生成的java類的內部方法進行反序列化)
try {
        PersonProto.Person parseFrom = PersonProto.Person.parseFrom(bytes);
} catch (InvalidProtocolBufferException e) {
        e.printStackTrace();
}

我們可以將此對象的序列化byte[]傳送給其他服務,讓他們通過同一個.proto文件生成相應語言的文件,通過內部的方法進行反序列化。

舉例

  • 兩個相互獨立的項目,但是會操作同一數據,并且數據進行緩存,可以將序列化byte[]存放在Redis
  • 不同語言通過的數據通信

ProtoBuf與Json的轉換
為什么要做這樣的轉換呢?因為我想到與數據庫對應的javabean是否可以轉換為proto數據這樣就可以進行數據進行后續的數據交互了。(當然也有其他方式,例如反射,和復制屬性等方式)

//通過fastJson進行轉換
String json = JsonUtil.toJson(person);

讓人意外的是,轉換失敗。提示信息一大堆,大致意思就是不能夠轉換。
通過ProtoBuf Util轉換
添加依賴

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util -->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java-util</artifactId>
    <version>3.6.1</version>
</dependency>

java代碼展示如下:

//to Json
JsonFormat.Printer printer = JsonFormat.printer();
String print = "";
try {
    print = printer.print(person);
    System.out.println(print);
} catch (InvalidProtocolBufferException e) {
    e.printStackTrace();
}

//to Object
JsonFormat.Parser parser = JsonFormat.parser();
try {
    PersonProto.Person.Builder newBuilder = PersonProto.Person.newBuilder();
    parser.merge(print, newBuilder);
    System.out.println(newBuilder.build());
} catch (InvalidProtocolBufferException e) {
    e.printStackTrace();
}

//添加java bean 此類對性數據庫的字段,同時與proto類屬性名相同
public class Person implements Serializable {
    private String name;
    private Integer age;
    private Boolean sex;
    private Date dirthday;//此處注意這里是時間類型而非proto類中的long類型
    private String address;
    private List<Car> cars = new ArrayList<Car>();
    private Map<String, String> other = new HashMap<String, String>();
}

public class Car implements Serializable {
    private String name;
    private String color;
}

//在上面的轉換中間添加以下代碼,發現同樣轉換成功
Person myPerson = JsonUtil.toObject(print, Person.class);
System.out.println(myPerson);
print = JsonUtil.toJson(myPerson);

總結:通過上面的測試,我們發現可以實現自定義的bean與proto是可以通過Json相互轉換的,然而它們之間的轉換需要第三方JSON轉換工具和protobuf util的支持。
上面可以應用于自定義的bean主要用于web前后臺的交互,同時通過JSON轉換也可以進行與其他服務的交互,數據可以放在redis中。
當然還有其他的轉換方式,如我上面所說:反射,和復制屬性。
反射可以通過屬性名稱一一對應進行轉換,在特定的情況下也是可以通過屬性類型進行轉換。
復制屬性,可以通過Spring的BeanUtils.copyProperties(Object source, Object target)方法進行轉換的。
后期我將補充此轉換方式的案例。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容