天天看點

java 序列化 protobuf_在Java中使用protobuf序列化對象

什麼是protobuf

它是一個對象序列化/反序列化的工具,什麼是對象的序列化/反序列化?就是把一個Java堆中存活的對象轉換成一串二進制編碼,然後該編碼可以用于本地存儲和網絡傳輸。反序列化就是根據一串二進制編碼還原出原來的那個對象,protobuf能夠将一個對象以特定的格式轉換為一個二進制串(序列化),然後将二進制串還原成對象(反序列化)。這裡涉及到兩個名額:

對同一個目标對象:

1)序列化和反序列化的時間開銷,

2)序列化之後串的長度

protobuf在這兩個方面都有非常出色的表現(網傳)

在Windows下使用protobuf的步驟如下:

第一步:

下載下傳protoc-2.5.0-win32.zip,得到其中的protoc.exe.然後将該protoc.exe的存放路徑加入Path環境變量,便于通路。比如,我的protoc.exe存放于D:/protobuf,環境變量中添加如下配置:

D:/protobuf

第二步:

編寫.proto檔案,它是序列化一個對象的“模闆”,protobuf就是根據它來決定如何序列化和反序列化。

編寫的person-entity.proto配置檔案如下:

option java_outer_classname = "PersonEntity";//生成的資料通路類的類名

message Person {

required int32 id= 1;//同上

required string name = 2;//必須字段,在後面的使用中必須為該段設定值

optional string email = 3;//可選字段,在後面的使用中可以自由決定是否為該字段設定值

}

message字段代表了一個對象,是以,可以使用message實作對象的嵌套序列化

required表示是強制字段,在後面的使用中必須為該字段設定值;

optional表示是可選字段,在後面的使用中可選地為該字段設定值;

repeated表示集合類型,可以填充多個資料

後面的1,2,3是字段的編号,字段名是讓使用者使用的,字段編号則是讓系統識别的,從1開始。

protobuf自定義的資料類型和Java的資料類型的對應關系見這篇部落格https://blog.csdn.net/chuhui1765/article/details/100670318

可以指定字段類型為其他的Message字段類型:

message SearchResponse{

repeated Result result= 1;

}

message Result{

requiredstring url = 1;

optionalstring title = 2;

repeatedstring snippets = 3;

}

可以在message内部嵌套另一個message:

message SearchResponse{

message Result{

requiredstring url = 1;

optionalstring title = 2;

repeatedstring snippets = 3;

}

repeated Result result= 1;

}

有關protobuf更詳細的用法,可以參考這篇優秀的博文https://worktile.com/tech/share/prototol-buffers

第三步:

用protoc.exe生成PersonEntity.class。打開DOS視窗,輸入如下的編譯指令:

protoc.exe -I=D:/protobuf --java_out=D:/protobuf D:/protobuf/person-entity.proto

編譯指令的格式如下:

protoc.exe -I=.protoc檔案的存放路徑 --java_out=.class檔案的輸出路徑 .protoc檔案的具體存放路徑

其中第三個路徑是必須的,而且要寫明.protoc檔案

最後會生成PersonEntity.java檔案,可以把它了解為一個工具類,幫助我們執行對象的序列化。

第四步:

建立maven項目,将PersonEntity.java複制到項目中,引入maven依賴

com.google.protobuf

protobuf-java

2.5.0

利用工具類序列化和反序列化對象。

import java.io.IOException;public classTest {public static voidmain(String[] args) throws IOException {//模拟将對象轉成byte[],友善傳輸

PersonEntity.Person.Builder builder =PersonEntity.Person.newBuilder();

builder.setId(1);

builder.setName("abc");

builder.setEmail("def");

PersonEntity.Person person=builder.build();

System.out.println("after :" +person.toString());

finalbyte[] bytes =person.toByteArray();

System.out.println("===========Person Byte==========");for(byteb : person.toByteArray()){

System.out.print(b);

}

System.out.println();//反序列化成Person類

byte[] byteArray =person.toByteArray();

PersonEntity.Person person2=PersonEntity.Person.parseFrom(byteArray);

System.out.println("after :" +person2.toString());

}

}

這裡還犯過一個錯:另外自定義一個Person類與PersonEntity.Person相對應。實際上在我們的項目中可以直接利用這個PersonEntity.Person來實作功能需求。

下面同樣以這個Person類序列化為例,來實際地比較通過各種不同的序列化方式得到的的位元組數組長度:

1.protobuf

序列化長度為:

System.out.println(person.getSerializedSize());

結果為:12

2.Java原生序列化

import java.io.*;public classTest2 {public static voidmain(String[] args) throws IOException {

Person p= new Person(1,"abc","def");

ObjectOutputStream objectOutputStream= newObjectOutputStream(new FileOutputStream("E:/object.txt"));

objectOutputStream.writeObject(p);

FileInputStream fileInputStream= new FileInputStream("E:/object.txt");

finalint length =fileInputStream.read();

System.out.println(length);

}

}

結果為:172

3.通過對象的toString()

序列化:重寫對象的toString()方法,按照特定的格式,将對象的各個字段封裝進一個String字元串,再将字元串按照特定的編碼方式,轉換成byte數組,實作本地存儲和網絡傳輸。

反序列化:對byte數組,重新轉換成字元串,再将字元串按照約定的格式,還原成對象的屬性,進而構造出原來的對象。

這裡重點探讨的是:String對象轉換成byte數組之後的長度

public static voidmain(String[] args) {

String s= "abc";

System.out.println(s.getBytes().length);

String s2= "嘤嘤嘤";

System.out.println(s2.getBytes().length);

System.out.println(Charset.defaultCharset());

}

輸出結果為:

3

9

UTF-8

可見,如果是英文字母每個字元占一個位元組,如果是漢字,每個字元占3個位元組。如果通過這種方式序列化,它的空間效率也是非常高的。

這裡有必要搞清楚Java預設的字元編碼集:

public byte[] getBytes() {return StringCoding.encode(value, 0, value.length);

}

Java采用平台的預設字元編碼集來進行字元與二進制byte序列的轉換。當然也可以在序列化的時候顯示指定字元編碼集。

Unicode 與 UTF-8

為什麼會出現unicode,這首先要從ASCII碼說起,它起源最早,用7位來表示英文字母、數字等字元。但是後來又出現了很多其他語言的字元,比如漢字,ASCII碼最多隻能表示256個字元,無法支援這些”異域“字元,是以出現了unicode碼,将各種語言的字元用一種統一的編碼方式轉換成01序列。但是新的問題又出現了,unicode編碼效率不高,原來一個字元a用ASCII表示隻需要一個字元,現在用unicode可能用3個字元,存儲效率和網絡傳輸效率大打折扣。是以出現了中間轉換編碼集,utf-8就是其中之一。”UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字元編碼,又稱萬國碼“,注意,它的編碼長度是可變的。是以,unicode是utf-8的基礎,utf-8是對unicode的一種解釋。