Java開發成長手冊,GitHub JavaEgg ,N線互聯網開發必備技能兵器譜,歡迎star + 指導
Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言數據標准 ,是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化(將 數據結構或對象 轉換成 二進制串 的過程 )。它很適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平台無關、可擴展的序列化結構數據格式
protocol buffers 誕生之初是為了解決服務器端新舊協議(高低版本)兼容性問題,名字也很體貼,“協議緩沖區”。只不過后期慢慢發展成用於傳輸數據。
筆者所在的360廣告投放,N億條商品信息的數據全部采用PB格式存儲、傳輸。
Protobuf 的優點
-
更小——序列化后,數據大小可縮小約3倍
-
更快——序列化速度更快,比xml和JSON快20-100倍,體積縮小后,傳輸時,帶寬也會優化
-
更簡單——proto編譯器,自動進行序列化和反序列化
-
維護成本低——跨平台、跨語言,多平台僅需要維護一套對象協議(.proto)
-
可擴展——“向后”兼容性好,不必破壞已部署的、依靠“老”數據格式的程序就可以對數據結構進行升級
-
加密性好——HTTP傳輸內容抓包只能看到字節
在傳輸數據量大、網絡環境不穩定的數據存儲和RPC數據交換場景比較合適
Protobuf 的不足
- 功能簡單,無法用來表示復雜的概念
- 通用性較差,XML和JSON已成為多種行業標准的編寫工具,pb只是geogle內部使用
- 自解釋性差,以二進制數據流方式存儲(不可讀),需要通過.proto文件才可以
官網 Protocol Buffer Basics: Java https://developers.google.com/protocol-buffers/docs/javatutorial
Hello World
1. 定義 .proto
文件的消息格式(你希望存儲的數據格式描述文件)
syntax = "proto2";
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
//消息模型
message Person {
//消息對象的字段:字段修飾符+字段類型+字段名稱+標識號(通過二進制格式唯一標識每個字段,不變可)
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
☆☆☆注:
- syntax = "proto2":指明版本
- package:PB的自己的包名,防止不同
.proto
項目間命名 發生沖突 - java_package: 生成java類的包名,如不顯式指定,默認包名為:按照應用名稱倒序方式進行排序
- java_outer_classname:生成 java類的類名,如不顯式指定,則默認為把.proto文件名轉換為首字母大寫來生成
- message: 你的消息格式,各數據類型(
bool
,int32
,float
,double
,string
,enum
... )字段的集合,在一個.proto文件中可以定義多個message,一個message里也可以定義另外一個message(相當於java的類,當然也可以有內部類) - 當然PB也是支持和java一樣的
import
的,import "xxx.proto";
- 像每個字段也必須有修飾符,PB提供的字段修飾符有3種
- required:必填
- optional:可選
- repeated :可重復字段,可放集合
- 標識號:通過二進制格式唯一標識每個字段 ,使用后就不能夠再改變
- 標識號使用范圍:[1,2的29次方 - 1]
- 不可使用 [19000-19999] 標識號, 因為
Protobuf
協議實現中對這些標識號進行了預留。假若使用,則會報錯 - 每個字段在進行編碼時都會占用內存,而 占用內存大小 取決於 標識號:
- 范圍 [1,15] 標識號的字段 在編碼時占用1個字節;
- 范圍 [16,2047] 標識號的字段 在編碼時占用2個字節
- 為頻繁出現的 消息字段 保留 [1,15] 的標識號
2. 使用 protocol buffer 編譯器(下載地址:https://github.com/protocolbuffers/protobuf/releases )
winows的話 cmd到編譯器安裝目錄的bin目錄中,執行 protoc.exe -h (E:\learning\protoc-3.9.0-win64\bin>protoc.exe -h),可以看到參數說明。
執行:protoc -I=源地址 --java_out=目標地址 源地址/xxx.proto
E:\learning\protoc-3.9.0-win64\bin>protoc.exe -I=E:\learning\ --java_out=E:\lear
ning\ E:\learning\addressbook.proto
實際使用中:
protoc.exe -I=E:\learn-workspace\starfish\starfish-learn\src\main\java\priv\starfish\ProtocolBuffers\proto\ --java_out=E:\learn-workspace\starfish\starfish-learn\src\main\java E:\learn-workspace\starfish\starfish-learn\src\main\java\priv\starfish\ProtocolBuffers\proto\addressbook.proto)
3. 通過 Java protocol buffer API 讀寫消息格式
package priv.starfish.ProtocolBuffers;
import com.google.protobuf.InvalidProtocolBufferException;
import priv.starfish.ProtocolBuffers.AddressBookProtos.Person;
import priv.starfish.ProtocolBuffers.AddressBookProtos.AddressBook;
import java.util.Arrays;
/**
* @author: starfish
* @date: 2019/7/24 14:39
* @description:
*/
public class HelloProto {
public static void main(String[] args) {
Person person = Person.newBuilder()
.setId(123)
.setName("starfish")
.setEmail("starfish@126.cn")
.addPhones(AddressBookProtos.Person.PhoneNumber.newBuilder()
.setType(AddressBookProtos.Person.PhoneType.HOME)
.setNumber("13555555555")
.build())
.build();
System.out.println(person.toString());
System.out.println(person.isInitialized());
try {
//序列化和反序列化
System.out.println(Arrays.toString(person.toByteArray()));
System.out.println(person.toByteString());
Person newPerson = Person.parseFrom(person.toByteArray());
System.out.println(newPerson);
newPerson = Person.parseFrom(person.toByteString());
System.out.println(newPerson);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
// 向地址簿添加兩條Person信息
AddressBook.Builder books = AddressBook.newBuilder();
books.addPeople(person);
books.addPeople(Person.newBuilder(person).setEmail("xin@163.com")
.build());
System.out.println("AddressBook對象信息:");
System.out.println(books.build());
}
}
編譯后生成的java類是不可變的,類似java的String,不可修改
構造消息,必須先構造一個builder,然后set屬性(可以一連串的set),最后調用build() 方法。
PB常用方法
-
isInitialized()
: 檢查必填字段(required)是否有set值 -
toString()
: 返回message的可讀字符串格式 -
mergeFrom(Message other)
: 合並message -
clear()
: 清空字段值 -
byte[] toByteArray();
: 序列化message,返回字節數組 -
MessageType parseFrom(byte[] data);
: 解析給定的字節數組 -
void writeTo(OutputStream output);
: 序列化message並寫入輸出流OutputStream
. -
MessageType parseFrom(InputStream input);
: 從輸入流InputStream
讀取並解析message
Reference: