1.簡介
Protocol Buffers是Google開發的一種數據描述語言,能夠將數據進行序列化,可用於數據存儲、通信協議等方面。
可以理解成更快、更簡單、更小的JSON或者XML,區別在於Protocol Buffers是二進制格式,而JSON和XML是文本格式。
相對於XML,Protocol Buffers有如下幾個優點:
1.簡潔。
2.體積小,消息大小只有XML的1/10到1/3。
3.速度快,解析速度比XML快20~100倍。
4.使用Protocol Buffers的編譯器,可以生成更容易在編程中使用的數據訪問代碼。
5.更好的兼容性,Protocol Buffers設計的一個原則就是要能夠很好的支持向下或向上兼容。
使用不同的數據描述語言序列化后的字節個數比對:
使用不同的數據描述語言進行序列化以及反序列化的響應時間比對:
*數據在網絡進行傳輸時要經歷三個階段: 發送方對數據進行序列化、網絡中傳輸、接收方反序列化。
將對象序列化成protobuf、xml、json結構時,protobuf所占的字節數量最少、有效數據的比重最大、總數據最少,因此決定了數據在網絡進行傳輸時所耗費的時間最少。
將對象序列化成protobuf、xml、json結構以及反序列化成對象時,protobuf所耗費的時間最少。
結論:數據使用protobuf序列化格式能夠大大提高生產效率(服務的響應時間)。
2.proto文件的語法規則
字段類型
*目前有v2、v3版本,不同版本的語法稍微有些不同,會額外進行說明,以下是v2版本的語法規則。
2.1 消息
1.使用message關鍵字定義消息,並指定消息的名稱(取一個有意義的名字)
2.指定字段的類型和名稱
3.添加字段的約束
4.定義字段的編號(從1開始,其中19000~19999被Protocol Buffers作為保留字段)
最基本的message
message User{ required int32 id = 1; required string username = 2; required string password = 3; optional string email = 4; }
字段約束
required指定該字段必須賦值。
optional表示該字段允許為空,可以使用[default]指定默認值,如果沒有指定默認值則會使用字段類型的默認值。
- 對於strings ,默認是一個空string。
- 對於bytes ,默認是一個空的bytes。
- 對於bools ,默認是false。
- 對於數值類型 ,默認是0。
- 對於枚舉,默認是第一個定義的枚舉值,必須為0。
repeated指定字段為集合。
oneof指定一組字段中必須有一個字段要賦值。
*在一個proto文件中可以同時定義多個message類型,生成代碼時根據生成代碼的目標語言不同,處理的方式不太一樣( 對於Java, 每個proto文件都生成一個類,即一個.java文件,每個message、enum類型都是該類的靜態內部類 )
message User{ required int32 id = 1; //username或email之間必須有一個字段要賦值
oneof login{ string username = 3; string email = 4; } required string password = 2; } message Admin{ required int32 id = 1; required string username = 2; required string password = 3; }
*可以指定字段的類型為其他的message類型。
message Course{ required User user = 1; required string cour_name = 2; } message User{ required int32 id = 1; required string username = 2; required string password = 3; optional string email = 4; }
*在proto文件中支持類型的嵌套,即定義的message類型僅作為包含其message類型的字段類型( 此時Course靜態內部類中包含User靜態內部類 )
message Course{ message User{ required int32 id = 1; required string username = 2; required string password = 3; optional string email = 4; } required User user = 1; required string cour_name = 2; }
*使用extensions關鍵字預留消息類型的字段編號,通過extend關鍵字繼續定義。
message User{ //30~100編號為User類型私有.
extensions 30 to 100 } extend User{ required int32 id = 1; required string username = 2; required string password = 3; optional string email = 4; }
2.2 枚舉
1.使用enum關鍵字定義枚舉,並指定枚舉的名稱(取一個有意義的名字)
2.設置枚舉可能包含的值並定義編號(從1開始,其中19000~19999被Protocol Buffers作為保留字段)
最基本的枚舉:
enum Course{ Chinese = 1; Mathematics = 2; English = 3; }
*可以使用import關鍵字導入其他proto文件。
*可以使用option java_package設置生成java類的包名。
*可以使用option java_outer_classname設置生成java類的類名。
import "other.proto"
option java_package = "com.zht.protobuf";
option java_outer_classname = "UserModel";
message User{
required int32 id = 1;
required string username = 2;
required string password = 3;
optional string email = 4;
}
3.proto2與proto3的不同
1.proto文件的第一行必須使用syntax屬性指定使用的protobuf版本:proto2、proto3。
2.移除了 “required” 字段約束。
3.“optional”字段約束改名為 “singular”。
4.在 proto2 中, "optional" 約束可以使用 default 指定字段的默認值(不指定也不賦值則跟隨系統), 在 proto3 中, 字段的默認值只能根據字段類型由系統決定。
*當字段被設置為默認值時, 該字段不會被序列化, 提高效率。
5.枚舉類型的第一個字段的編號必須為 0 。
4.protobuf的使用
1.環境的准備
在github下載對應操作環境的protobuf工具包: https://github.com/google/protobuf/releases
windows用戶選擇: protoc-3.5.1-win32.zip
解壓后配置環境變量PATH,使其在上下文能直接搜索 protoc.exe。
2.編寫.proto文件
E:\proto\user.proto
內容如下:
#v3版本需要在proto文件的第一行使用syntax屬性指定proto文件使用的語法的版本
syntax = "proto2";
option java_package = "com.zht.protobuf";
option java_outer_classname = "UserModel";
message User{
required int32 id = 1;
required string username = 2;
required string password = 3;
optional string email = 4;
}
3.使用protoc.exe命令生成實體
protoc.exe -I [proto文件所在目錄] --java_out [JAVA類存放目錄] [proto文件絕對路徑]
4.將實體放入工程進行實體的構造和賦值
將實體放入工程:
構造並且賦值:
public class Main { public static void main(String[] args) throws InvalidProtocolBufferException { //獲取構造器並進行賦值 UserModel.User.Builder builder = UserModel.User.newBuilder(); builder.setId(1); builder.setUsername("zhuanght"); builder.setPassword("123456"); builder.setEmail("aiuzht119@163.com"); //獲取實體 UserModel.User user = builder.build(); System.out.println("源數據:\r"+ user.toString()); System.out.println("序列化后:"+Arrays.toString(user.toByteArray())); //模擬接收Byte[],反序列化成User實體 byte[] data =user.toByteArray(); User u = User.parseFrom(data); System.out.println("\r解析:\r" +u.toString()); } }
打印結果 :
源數據:
id: 1
username: "zhuanght"
password: "123456"
email: "aiuzht119@163.com"
序列化后:[8, 1, 18, 8, 122, 104, 117, 97, 110, 103, 104, 116, 26, 6, 49, 50, 51, 52, 53, 54, 34, 17, 97, 105, 117, 122, 104, 116, 49, 49, 57, 64, 49, 54, 51, 46, 99, 111, 109]
解析:
id: 1
username: "zhuanght"
password: "123456"
email: "aiuzht119@163.com"
5.獲取序列化后的字節數組在網絡中進行傳輸
5.protobuf實例與json進行轉換
導入相關依賴
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
protobuf實例序列化為json格式
//user為上面例子的UserModel.User消息實例
String json = JsonFormat.printToString(user)
json序列化為protobuf實例
//builder為上面例子的UserModel.User.Builder構造器實例
JsonFormat.merge(json ,builder); //此時再使用構建器創建的實例就包含轉換后的數據
builder.build();