版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程目錄Protobuf介紹編寫Protobuf頭部全局定義消息結(jié)構(gòu)具體定義字段類型定義編譯Protobuf使用Protobuf構(gòu)造消息對(duì)象序列化、反序列化Protobuf為什么高效序列化大小對(duì)比序列化速度對(duì)比為什么高效?總結(jié)當(dāng)涉及到網(wǎng)絡(luò)通信和數(shù)據(jù)存儲(chǔ)時(shí),數(shù)據(jù)序列化一直都是一個(gè)重要的話題;特別是現(xiàn)在很多公司都在推行微服務(wù),數(shù)據(jù)序列化更是重中之重,通常會(huì)選擇使用JSON作為數(shù)據(jù)交換格式,且JSON已經(jīng)成為業(yè)界的主流。但是Google這么大的公司使用的卻是一種被稱為Protobuf的數(shù)據(jù)交換格式,它是有什么優(yōu)勢(shì)嗎?這篇文章介紹Protobuf的相關(guān)知識(shí)。
GitHub:/protocolbuffers/protobuf
官方文檔:https://protobuf.dev/overview/
Protobuf介紹
Protobuf(ProtocolBuffers)是由Google開(kāi)發(fā)的一種輕量級(jí)、高效的數(shù)據(jù)交換格式,它被用于結(jié)構(gòu)化數(shù)據(jù)的序列化、反序列化和傳輸。相比于XML和JSON等文本格式,Protobuf具有更小的數(shù)據(jù)體積、更快的解析速度和更強(qiáng)的可擴(kuò)展性。
Protobuf的核心思想是使用協(xié)議(Protocol)來(lái)定義數(shù)據(jù)的結(jié)構(gòu)和編碼方式。使用Protobuf,可以先定義數(shù)據(jù)的結(jié)構(gòu)和各字段的類型、字段等信息,然后使用Protobuf提供的編譯器生成對(duì)應(yīng)的代碼,用于序列化和反序列化數(shù)據(jù)。由于Protobuf是基于二進(jìn)制編碼的,因此可以在數(shù)據(jù)傳輸和存儲(chǔ)中實(shí)現(xiàn)更高效的數(shù)據(jù)交換,同時(shí)也可以跨語(yǔ)言使用。
相比于XML和JSON,Protobuf有以下幾個(gè)優(yōu)勢(shì):
更小的數(shù)據(jù)量:Protobuf的二進(jìn)制編碼通常比XML和JSON小3-10倍,因此在網(wǎng)絡(luò)傳輸和存儲(chǔ)數(shù)據(jù)時(shí)可以節(jié)省帶寬和存儲(chǔ)空間。更快的序列化和反序列化速度:由于Protobuf使用二進(jìn)制格式,所以序列化和反序列化速度比XML和JSON快得多??缯Z(yǔ)言:Protobuf支持多種編程語(yǔ)言,可以使用不同的編程語(yǔ)言來(lái)編寫客戶端和服務(wù)端。這種跨語(yǔ)言的特性使得Protobuf受到很多開(kāi)發(fā)者的歡迎(JSON也是如此)。易于維護(hù)可擴(kuò)展:Protobuf使用.proto文件定義數(shù)據(jù)模型和數(shù)據(jù)格式,這種文件比XML和JSON更容易閱讀和維護(hù),且可以在不破壞原有協(xié)議的基礎(chǔ)上,輕松添加或刪除字段,實(shí)現(xiàn)版本升級(jí)和兼容性。
編寫Protobuf
使用Protobuf的語(yǔ)言定義文件(.proto)可以定義要傳輸?shù)男畔⒌臄?shù)據(jù)結(jié)構(gòu),可以包括各個(gè)字段的名稱、類型等信息。同時(shí)也可以相互嵌套組合,構(gòu)造出更加復(fù)雜的消息結(jié)構(gòu)。
比如想要構(gòu)造一個(gè)地址簿AddressBook信息結(jié)構(gòu)。一個(gè)AddressBook可以包含多個(gè)人員Person信息,每個(gè)Person信息可以包含id、name、email信息,同時(shí)一個(gè)Person也可以包含多個(gè)電話號(hào)碼信息PhoneNumber,每個(gè)電話號(hào)碼信息需要指定號(hào)碼種類,如手機(jī)、家庭電話、工作電話等。
如果使用Protobuf編寫定義文件如下:
//文件:to
syntax="proto3";
//指定protobuf包名,防止有相同類名的message定義
packagetobuf;
//是否生成多個(gè)文件
optionjava_multiple_files=true;
//生成的文件存放在哪個(gè)包下
optionjava_package="tos";
//生成的類名,如果沒(méi)有指定,會(huì)根據(jù)文件名自動(dòng)轉(zhuǎn)駝峰來(lái)命名
optionjava_outer_classname="AddressBookProtos";
messagePerson{
//=1,=2作為序列化后的二進(jìn)制編碼中的字段的唯一標(biāo)簽,也因此,1-15比16會(huì)少一個(gè)字節(jié),所以盡量使用1-15來(lái)指定常用字段。
optionalint32id=1;
optionalstringname=2;
optionalstringemail=3;
enumPhoneType{
MOBILE=0;
HOME=1;
WORK=2;
messagePhoneNumber{
optionalstringnumber=1;
optionalPhoneTypetype=2;
repeatedPhoneNumberphones=4;
messageAddressBook{
repeatedPersonpeople=1;
Protobuf文件中的語(yǔ)法解釋。
頭部全局定義
syntax=proto3指定Protobuf版本為版本3(最新版本)packagetobuf;指定Protobuf包名,防止有相同類名的message定義,這個(gè)包名是生成的類中所用到的一些信息的前綴,并非類所在包。optionjava_multiple_files=true;是否生成多個(gè)文件。若false,則只會(huì)生成一個(gè)類,其他類以內(nèi)部類形式提供。optionjava_package=生成的類所在包。optionjava_outer_classname生成的類名,若無(wú),自動(dòng)使用文件名進(jìn)行駝峰轉(zhuǎn)換來(lái)為類命名。
消息結(jié)構(gòu)具體定義
messagePerson定一個(gè)了一個(gè)Person類。
Person類中的字段被optional修飾,被optional修飾說(shuō)明字段可以不賦值。
修飾符optional表示可選字段,可以不賦值。修飾符repeated表示數(shù)據(jù)重復(fù)多個(gè),如數(shù)組,如List。修飾符required表示必要字段,必須給值,否則會(huì)報(bào)錯(cuò)RuntimeException,但是在Protobuf版本3中被移除。即使在版本2中也應(yīng)該慎用,因?yàn)橐坏┒x,很難更改。
字段類型定義
修飾符后面緊跟的是字段類型,如int32、string。常用的類型如下:
int32、int64、uint32、uint64:整數(shù)類型,包括有符號(hào)和無(wú)符號(hào)類型。float、double:浮點(diǎn)數(shù)類型。bool:布爾類型,只有兩個(gè)值,true和false。string:字符串類型。bytes:二進(jìn)制數(shù)據(jù)類型。enum:枚舉類型,枚舉值可以是整數(shù)或字符串。message:消息類型,可以嵌套其他消息類型,類似于結(jié)構(gòu)體。
字段后面的=1,=2是作為序列化后的二進(jìn)制編碼中的字段的對(duì)應(yīng)標(biāo)簽,因?yàn)镻rotobuf消息在序列化后是不包含字段信息的,只有對(duì)應(yīng)的字段序號(hào),所以節(jié)省了空間。也因此,1-15比16會(huì)少一個(gè)字節(jié),所以盡量使用1-15來(lái)指定常用字段。且一旦定義,不要隨意更改,否則可能會(huì)對(duì)不上序列化信息。
編譯Protobuf
使用Protobuf提供的編譯器,可以將.proto文件編譯成各種語(yǔ)言的代碼文件(如Java、C++、Python等)。
下載編譯器:/protocolbuffers/protobuf/releases/latest
安裝完成后可以使用protoc命令編譯proto文件,如編譯示例中的to.
protoc--java_out=./java./resources/to
生成后可以看到生產(chǎn)的類文件。
./
├──java
│└──com
│└──wdbyte
│└──tool
│├──protos
││├──AddressBook.java
││├──AddressBookOrBuilder.java
││├──AddressBookProtos.java
││├──Person.java
││├──PersonOrBuilder.java
└──resources
├──to
使用Protobuf
使用Java語(yǔ)言操作Protobuf,首先需要引入Protobuf依賴。
Maven依賴:
dependency
groupIdtobuf/groupId
artifactIdprotobuf-java/artifactId
version3.22.3/version
/dependency
構(gòu)造消息對(duì)象
//直接構(gòu)建
PhoneNumberphoneNumber1=PhoneNumber.newBuilder().setNumber().setType(PhoneType.HOME).build();
Personperson1=Person.newBuilder().setId(1).setName("").setEmail("xxx@").addPhones(phoneNumber1).build();
AddressBookaddressBook1=AddressBook.newBuilder().addPeople(person1).build();
System.out.println(addressBook1);
System.out.println("------------------");
//鏈?zhǔn)綐?gòu)建
AddressBookaddressBook2=AddressBook
.newBuilder()
.addPeople(Person.newBuilder()
.setId(2)
.setName("")
.setEmail("yyy@126.com")
.addPhones(PhoneNumber.newBuilder()
.setNumber()
.setType(PhoneType.HOME)
.build();
System.out.println(addressBook2);
輸出:
people{
id:1
name:
email:xxx@
phones{
number/p>
type:HOME
}
}
------------------
people{
id:2
name:
email:yyy@126.com
phones{
number/p>
type:HOME
}
}
序列化、反序列化
序列化:將內(nèi)存中的數(shù)據(jù)對(duì)象序列化為二進(jìn)制數(shù)據(jù),可以用于網(wǎng)絡(luò)傳輸或存儲(chǔ)等場(chǎng)景。
反序列化:將二進(jìn)制數(shù)據(jù)反序列化成內(nèi)存中的數(shù)據(jù)對(duì)象,可以用于數(shù)據(jù)處理和業(yè)務(wù)邏輯。
下面演示使用Protobuf進(jìn)行字符數(shù)組和文件的序列化及反序列化過(guò)程。
packagetos;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.IOException;
*@author
publicclassProtobufTest2{
publicstaticvoidmain(String[]args)throwsIOException{
PhoneNumberphoneNumber1=PhoneNumber.newBuilder().setNumber().setType(PhoneType.HOME).build();
Personperson1=Person.newBuilder().setId(1).setName("").setEmail("xxx@").addPhones(phoneNumber1).build();
AddressBookaddressBook1=AddressBook.newBuilder().addPeople(person1).build();
//序列化成字節(jié)數(shù)組
byte[]byteArray=addressBook1.toByteArray();
//反序列化-字節(jié)數(shù)組轉(zhuǎn)對(duì)象
AddressBookaddressBook2=AddressBook.parseFrom(byteArray);
System.out.println("字節(jié)數(shù)組反序列化:");
System.out.println(addressBook2);
//序列化到文件
addressBook1.writeTo(newFileOutputStream("AddressBook1.txt"));
//讀取文件反序列化
AddressBookaddressBook3=AddressBook.parseFrom(newFileInputStream("AddressBook1.txt"));
System.out.println("文件讀取反序列化:");
System.out.println(addressBook3);
輸出:
字節(jié)數(shù)組反序列化:
people{
id:1
name:
email:xxx@
phones{
number/p>
type:HOME
}
}
文件讀取反序列化:
people{
id:1
name:
email:xxx@
phones{
number/p>
type:HOME
}
}
Protobuf為什么高效
在分析Protobuf高效之前,我們先確認(rèn)一下Protobuf是否真的高效,下面將Protobuf與JSON進(jìn)行對(duì)比,分別對(duì)比序列化和反序列化速度以及序列化后的存儲(chǔ)占用大小。
測(cè)試工具:JMH,F(xiàn)astJSON,
測(cè)試對(duì)象:Protobuf的to,JSON的普通Java類。
Maven依賴:
dependency
groupIdcom.alibaba/groupId
artifactIdfastjson/artifactId
version2.0.7/version
/dependency
dependency
groupIdorg.openjdk.jmh/groupId
artifactIdjmh-core/artifactId
version1.33/version
/dependency
dependency
groupIdorg.openjdk.jmh/groupId
artifactIdjmh-generator-annprocess/artifactId
version1.33/version
scopeprovided/scope
/dependency
先編寫與to結(jié)構(gòu)相同的Java類AddressBookJava.java.
publicclassAddressBookJava{
ListPersonJavapersonJavaList;
publicstaticclassPersonJava{
privateintid;
privateStringname;
privateStringemail;
privatePhoneNumberJavaphones;
//get...set...
publicstaticclassPhoneNumberJava{
privateStringnumber;
privatePhoneTypeJavaphoneTypeJava;
//get....set....
publicenumPhoneTypeJava{
MOBILE,HOME,WORK;
publicListPersonJavagetPersonJavaList(){
returnpersonJavaList;
publicvoidsetPersonJavaList(ListPersonJavapersonJavaList){
this.personJavaList=personJavaList;
序列化大小對(duì)比
分別在地址簿中添加1000個(gè)人員信息,輸出序列化后的數(shù)組大小。
packagetos;
importjava.io.IOException;
importjava.util.ArrayList;
importcom.alibaba.fastjson.JSON;
importtos.AddressBook.Builder;
importtos.AddressBookJava.PersonJava;
importtos.AddressBookJava.PhoneNumberJava;
importtos.AddressBookJava.PhoneTypeJava;
importtos.Person.PhoneNumber;
importtos.Person.PhoneType;
*@author
publicclassProtobufTest3{
publicstaticvoidmain(String[]args)throwsIOException{
AddressBookJavaaddressBookJava=createAddressBookJava(1000);
StringjsonString=JSON.toJSONString(addressBookJava);
System.out.println("jsonstringsize:"+jsonString.length());
AddressBookaddressBook=createAddressBook(1000);
byte[]addressBookByteArray=addressBook.toByteArray();
System.out.println("protobufbytearraysize:"+addressBookByteArray.length);
publicstaticAddressBookcreateAddressBook(intpersonCount){
Builderbuilder=AddressBook.newBuilder();
for(inti=0;ipersonCount;i++){
builder.addPeople(Person.newBuilder()
.setId(i)
.setName("")
.setEmail("xxx@126.com")
.addPhones(PhoneNumber.newBuilder()
.setNumber()
.setType(PhoneType.HOME)
returnbuilder.build();
publicstaticAddressBookJavacreateAddressBookJava(intpersonCount){
AddressBookJavaaddressBookJava=newAddressBookJava();
addressBookJava.setPersonJavaList(newArrayList());
for(inti=0;ipersonCount;i++){
PersonJavapersonJava=newPersonJava();
personJava.setId(i);
personJava.setName("");
personJava.setEmail("xxx@126.com");
PhoneNumberJavanumberJava=newPhoneNumberJava();
numberJava.setNumber();
numberJava.setPhoneTypeJava(PhoneTypeJava.HOME);
personJava.setPhones(numberJava);
addressBookJava.getPersonJavaList().add(personJava);
returnaddressBookJava;
輸出:
jsonstringsize:108910
protobufbytearraysize:50872
可見(jiàn)測(cè)試中Protobuf的序列化結(jié)果比JSON小了將近一倍左右。
序列化速度對(duì)比
使用JMH進(jìn)行性能測(cè)試,分別測(cè)試JSON的序列化和反序列以及Protobuf的序列化和反序列化性能情況。每次測(cè)試前進(jìn)行3次預(yù)熱,每次3秒。接著進(jìn)行5次測(cè)試,每次3秒,收集測(cè)試情況。
packagetos;
importjava.util.ArrayList;
importjava.util.concurrent.TimeUnit;
importcom.alibaba.fastjson.JSON;
importtobuf.InvalidProtocolBufferException;
importtos.AddressBook.Builder;
importtos.AddressBookJava.PersonJava;
importtos.AddressBookJava.PhoneNumberJava;
importtos.AddressBookJava.PhoneTypeJava;
importtos.Person.PhoneNumber;
importtos.Person.PhoneType;
importorg.openjdk.jmh.annotations.Benchmark;
importorg.openjdk.jmh.annotations.BenchmarkMode;
importorg.openjdk.jmh.annotations.Fork;
importorg.openjdk.jmh.annotations.Measurement;
importorg.openjdk.jmh.annotations.Mode;
importorg.openjdk.jmh.annotations.OutputTimeUnit;
importorg.openjdk.jmh.annotations.Scope;
importorg.openjdk.jmh.annotations.Setup;
importorg.openjdk.jmh.annotations.State;
importorg.openjdk.jmh.annotations.Warmup;
*@author
@State(Scope.Thread)
@Fork(2)
@Warmup(iterations=3,time=3)
@Measurement(iterations=5,time=3)
@BenchmarkMode(Mode.Throughput)//Throughput:吞吐量,SampleTime:采樣時(shí)間
@OutputTimeUnit(TimeUnit.MILLISECONDS)
publicclassProtobufTest4{
privateAddressBookJavaaddressBookJava;
privateAddressBookaddressBook;
@Setup
publicvoidinit(){
addressBookJava=createAddressBookJava(1000);
addressBook=createAddressBook(1000);
@Benchmark
publicAddressBookJavatestJSON(){
//轉(zhuǎn)JSON
StringjsonString=JSON.toJSONString(addressBookJava);
//JSON轉(zhuǎn)對(duì)象
returnJSON.parseObject(jsonString,AddressBookJava.class);
@Benchmark
publicAddressBooktestProtobuf()throwsInvalidProtocolBufferException{
//轉(zhuǎn)JSON
byte[]addressBookByteArray=addressBook.toByteArray();
//JSON轉(zhuǎn)對(duì)象
returnAddressBook.parseFrom(addressBookByteArray);
publicstaticAddressBookcreateAddressBook(intpersonCount){
Builderbuilder=AddressBook.newBuilder();
for(inti=0;ipersonCount;i++){
builder.addPeople(Person.newBuilder()
.setId(i)
.setName("")
.setEmail("xxx@126.com")
.addPhones(PhoneNumber.newBuilder()
.setNumber()
.setType(PhoneType.HOME)
returnbuilder.build();
publicstaticAddressBookJavacreateAddressBookJava(intpersonCount){
AddressBookJavaaddressBookJava=newAddressBookJava();
addressBookJava.setPersonJavaList(newArrayList());
for(inti=0;ipersonCount;i++){
PersonJavapersonJava=newPersonJava();
personJava.setId(i);
personJava.setName("");
personJava.setEmail("xxx@126.com");
PhoneNumberJavanumberJava=newPhoneNumberJava();
numberJava.setNumber();
numberJava.setPhoneTypeJava(PhoneTypeJava.HOME);
personJava.setPhones(numberJava);
addressBookJava.getPersonJavaList().add(personJava);
returnaddressBookJava;
JMH吞吐量測(cè)試結(jié)果(Score值越大吞吐量越高,性能越好):
BenchmarkModeCntScoreErrorUnits
ProtobufTest3.testJSONthrpt101.8770.287ops/ms
ProtobufTest3.testProtobufthrpt102.8130.446ops/ms
JMH采樣時(shí)間測(cè)試結(jié)果(Score越小,采樣時(shí)間越小,性能越好):
BenchmarkModeCntScoreErrorUnits
ProtobufTest3.testJSONsample530280.5650.005ms/op
ProtobufTest3.testProtobufsample904130.3320.001ms/op
從測(cè)試結(jié)果看,不管是吞吐量測(cè)試,還是采樣時(shí)間測(cè)試,Protobuf都優(yōu)于JSON。
為什么高效?
Protobuf是如何實(shí)現(xiàn)這種高效緊湊的數(shù)據(jù)編碼和解碼的呢?
首先,Protobuf使用二進(jìn)制編碼,會(huì)提高性能;其次Protobuf在將數(shù)據(jù)轉(zhuǎn)換
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- GB/T 3215-2025石油、石化和天然氣工業(yè)用離心泵
- GB/T 32219-2026筒式磨機(jī)鑄造磨段
- 創(chuàng)業(yè)培訓(xùn)教師授課信息反饋表
- 平臺(tái)穩(wěn)性操作員保密競(jìng)賽考核試卷含答案
- 制材工安全文明測(cè)試考核試卷含答案
- 軋鋼精整工安全宣貫強(qiáng)化考核試卷含答案
- 橋梁支座試驗(yàn)培訓(xùn)
- 銀行內(nèi)部審計(jì)報(bào)告制度
- 酒店員工獎(jiǎng)懲與激勵(lì)機(jī)制制度
- 超市員工績(jī)效考核制度
- 北京市順義區(qū)2025-2026學(xué)年八年級(jí)上學(xué)期期末考試英語(yǔ)試題(原卷版+解析版)
- 中學(xué)生冬季防溺水主題安全教育宣傳活動(dòng)
- 2026年藥廠安全生產(chǎn)知識(shí)培訓(xùn)試題(達(dá)標(biāo)題)
- 初中九年級(jí)上一元二次方程計(jì)算練習(xí)題及答案詳解B2
- 中國(guó)涉外律師人才研究報(bào)告2025
- 冷庫(kù)防護(hù)制度規(guī)范
- 2026年生產(chǎn)管理崗入職性格測(cè)試題及答案
- 2026年bjt商務(wù)能力考試試題
- 廣東省廣州市番禺區(qū)2026屆高一數(shù)學(xué)第一學(xué)期期末聯(lián)考試題含解析
- 2026年廣東省佛山市高三語(yǔ)文聯(lián)合診斷性考試作文題及3篇范文:可以“重讀”甚至“重構(gòu)”這些過(guò)往
- 老年住院患者非計(jì)劃性拔管分析2026
評(píng)論
0/150
提交評(píng)論