貴陽網(wǎng)站建設(shè)zbcskj重慶seo招聘
文章目錄
- 1.認(rèn)識 ProtoBuf
- 2. 安裝ProtoBuf
- 3. 快速上手 ProtoBuf
- 4. proto3 語法
- 5. probuf 實(shí)戰(zhàn)
- 6. 總結(jié)
1.認(rèn)識 ProtoBuf
?
在認(rèn)識 啥是 ProtoBuf 之前我們先來 回顧一下 (或 了解 一下 啥是 序列化)
?
序列化概念回顧 :
?
圖一 :
?
回顧 序列化 ,下面提出一個(gè)問題 如何來實(shí)現(xiàn)序列化
?
答 : XML , JSON , ProtoBuf 等.
?
通過 如何 實(shí)現(xiàn)序列化 就引出了 ProtoBuf , 關(guān)于 ProtoBuf 其實(shí)就是 幫助我們實(shí)現(xiàn)序列化的一種手段.
?
簡單認(rèn)識了一下 ProtoBuf ,下面就來看看 ProtoBuf 的特點(diǎn) .
?
ProtoBuf 的自身特點(diǎn) :
- 語??關(guān)、平臺?關(guān):即?ProtoBuf??持?Java、C++、Python?等多種語?,?持多個(gè)平臺。?
- ?效:即??XML?更?、更快、更為簡單。
- 擴(kuò)展性、兼容性好:你可以更新數(shù)據(jù)結(jié)構(gòu),?不影響和破壞原有的舊程序。
?
ProtoBuf 的使用特點(diǎn)
- ProtoPuf 是需要依賴 通過 編譯生的 java 代碼來使用的.
?
這里 想一下 我們自己實(shí)現(xiàn)一個(gè)序列化 要如何做 (在 Java 語言下) :
?
上面就是 ProtoBuf 的使用流程 ,下面在來看看 ProtoBuf 的使用特點(diǎn) : ProtoPuf 是需要依賴 通過 編譯生的 java 代碼來使用的.
?
引用 : ProtoBuf 完整流程圖
?
2. 安裝ProtoBuf
?
下載地址
?
Windows環(huán)境下安裝ProtoBuf
?
圖一 :
?
圖二 :
?
Linux 下安裝 ProtoBuf
?
CentOs 環(huán)境
?
使用命令 : sudo yum install autoconf automake libtool curl make gcc-c++ unzip
?
圖一 :
?
圖二 :
# 第?步執(zhí)?autogen.sh,但如果下載的是具體的某??語?,不需要執(zhí)?這?步。
./autogen.sh# 第?步執(zhí)?configure,有兩種執(zhí)??式,任選其?即可,如下:# 1、protobuf默認(rèn)安裝在 /usr/local ?錄,lib、bin都是分散的
./configure# 2、修改安裝?錄,統(tǒng)?安裝在/usr/local/protobuf下
./configure --prefix=/usr/local/protobuf
?
圖一 :
?
圖二 :
sudo vim /etc/profile# 添加內(nèi)容如下:#(動(dòng)態(tài)庫搜索路徑) 程序加載運(yùn)?期間查找動(dòng)態(tài)鏈接庫時(shí)指定除了系統(tǒng)默認(rèn)路徑之外的其他路徑
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/#(靜態(tài)庫搜索路徑) 程序編譯期間查找動(dòng)態(tài)鏈接庫時(shí)指定查找共享庫的路徑
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/#執(zhí)?程序搜索路徑
export PATH=$PATH:/usr/local/protobuf/bin/#c程序頭?件搜索路徑
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/#c++程序頭?件搜索路徑
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/#pkg-config 路徑
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
?
最后?步,重新執(zhí)? /etc/profile ?件: source /etc/profile
?
到此 在 Linux 下 就安裝完 ProtoBuf , 下面就來學(xué)習(xí)一下如何快速上手 ProtoBuf 。
?
3. 快速上手 ProtoBuf
?
關(guān)于 快速 上手 ProtoBuf , 這里有兩個(gè)目的 :
- 體驗(yàn) ProtoBuf 的使用流程
- l了解 ProtoBuf 的基礎(chǔ)語法
?
這里來實(shí)現(xiàn) 一個(gè) 通訊錄 1.0 來 了解 ProtoBuf 的使用流程 及 ProtoBuf 的基礎(chǔ)語法.
?
需求 :
- 對?個(gè)聯(lián)系?的信息使? PB 進(jìn)?序列化,并將結(jié)果打印出來。
- 對序列化后的內(nèi)容使? PB 進(jìn)?反序列,解析出聯(lián)系信息并打印出來。
- 聯(lián)系?包含以下信息: 姓名、年齡。
?
在編寫代碼之前 ,我們先來下載一個(gè)插件 :
?
第一步 : 創(chuàng)建一個(gè) Maven 項(xiàng)目
?
2.進(jìn)入 jar 包
<!-- protobuf ?持 Java 核?包 -->
<dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>${protobuf.version}</version>
</dependency>
?
注意 : 這里 使用的 jar 包 版本 需要和我們安裝的 protobuf 版本一致 ,如 文章中使用的 3.21.11
?
3.創(chuàng)建 對應(yīng)的包 和 文件
?
下面就來 編寫我們的代碼 , 這里先來說幾個(gè)基本的語法點(diǎn)
?
在首行指定語法版本
?
添加文件選項(xiàng)
?
在 .proto 文件中 可以聲明許多選項(xiàng) 使用 option
標(biāo)注 選項(xiàng)能影響?proto?編譯器的某些處理?式。
?
上面先簡單寫幾個(gè)選項(xiàng), 等后面說 proto3 的在詳細(xì)介紹
?
完成上面的準(zhǔn)本工作,下面就可以定義我們的聯(lián)系人 message .
?
關(guān)于為啥要定義消息(message) 之前是說過的 ,在 認(rèn)識ProtoBuf 中 提到過 , 這里再簡單說一說 .
消息(message): 要定義的結(jié)構(gòu)化對象,我們可以給這個(gè)結(jié)構(gòu)化對象中定義其對應(yīng)的屬性內(nèi)容。在?絡(luò)傳輸中,我們需要為傳輸雙?定制協(xié)議。
?
定制協(xié)議說?了就是定義結(jié)構(gòu)體或者結(jié)構(gòu)化數(shù)據(jù),
?
?如,tcp,udp報(bào)?就是結(jié)構(gòu)化的。再?如將數(shù)據(jù)持久化存儲(chǔ)到數(shù)據(jù)庫時(shí),會(huì)將?系列元數(shù)據(jù)統(tǒng)??對象組織起來,再進(jìn)?存儲(chǔ)
?
所以ProtoBuf就是以message的?式來?持我們定制協(xié)議字段,后期幫助我們形成類和?法來使?。
?
在通訊錄1.0中我們就需要為聯(lián)系?定義?個(gè) message。
?
定義消息字段
在message中我們可以定義其屬性字段,字段定義格式為:字段類型 字段名=字段唯?編號;
- 字段名稱命名規(guī)范:全?寫字?,多個(gè)字?之間?
_
連接。 - 字段類型分為:標(biāo)量數(shù)據(jù)類型 和 特殊類型(包括枚舉、其他消息類型等)。
- 字段唯?編號:?來標(biāo)識字段,?旦開始使?就不能夠再改變。
?
該表格展?了定義于消息體中的標(biāo)量數(shù)據(jù)類型,以及編譯 .proto ?件之后?動(dòng)?成的類中與之對應(yīng)的字段類型。在這?展?了與 JAVA 語?對應(yīng)的類型
.proto Type | Notes | Java Type |
---|---|---|
double | double | |
float | float | |
int32 | 使?變?編碼[1]。負(fù)數(shù)的編碼效率較低?若字段可 能為負(fù)值,應(yīng)使? sint32 代替。 | int |
int64 | 使?變?編碼[1]。負(fù)數(shù)的編碼效率較低?若字段可 能為負(fù)值,應(yīng)使? sint64 代替。 | long |
uint32 | 使?變?編碼[1]。 | int[2] |
uint64 | 使?變?編碼[1]。 | long[2] |
sint32 | 使?變?編碼[1]。符號整型。負(fù)值的編碼效率?于 常規(guī)的 int32 類型。 | int |
sint64 | 使?變?編碼[1]。符號整型。負(fù)值的編碼效率?于 常規(guī)的 int64 類型。 | long |
fixed32 | 定? 4 字節(jié)。若值常?于2^28 則會(huì)? uint32 更? 效。 | int |
fixed64 | 定? 8 字節(jié)。若值常?于2^56 則會(huì)? uint64 更? 效。 | long |
sfixed32 | 定? 4 字節(jié)。 | int |
sfixed64 | 定? 8 字節(jié)。 | long |
string | 包含 UTF-8 和 ASCII 編碼的字符串,?度不能超過 2^32 。 | String |
bytes | 可包含任意的字節(jié)序列但?度不能超過 2^32 。 | ByteString |
bool | boolean |
?
[1] 變?編碼是指:經(jīng)過protobuf 編碼后,原本4字節(jié)或8字節(jié)的數(shù)可能會(huì)被變?yōu)槠渌止?jié)數(shù)。
[2] 在 Java 中,?符號 32 位和?符號 64 位整數(shù)使?它們對應(yīng)的有符號整數(shù)來表?,這時(shí)第?個(gè) bit 位僅是簡單地存儲(chǔ)在符號位中。
?
簡單了解看完 標(biāo)量數(shù)據(jù)類型 ,這里需要著重 的說一下 字符唯一編號的范圍
?
1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可?。
?
19000 ~ 19999 不可?是因?yàn)?#xff1a;在 Protobuf 協(xié)議的實(shí)現(xiàn)中,對這些數(shù)進(jìn)?了預(yù)留。
?
如果?要在.proto ?件中使?這些預(yù)留標(biāo)識號,例如將 name 字段的編號設(shè)置為19000,編譯時(shí)就會(huì)報(bào)警:?
// 消息中定義了如下編號,代碼會(huì)告警:// Field numbers 19,000 through 19,999 are reserved for the protobuf implementationstring name = 19000;
?
這里 編譯 proto 文件 還沒學(xué), 這里先簡單的 看一下 定義 編號為 19000 編譯后的錯(cuò)誤?
?
最后 值得?提的是,范圍為 1 ~ 15 的字段編號需要?個(gè)字節(jié)進(jìn)?編碼, 16 ~ 2047 內(nèi)的數(shù)字需要兩個(gè)字節(jié) 進(jìn)?編碼。編碼后的字節(jié)不僅
?
只包含了編號,還包含了字段類型。所以 1 ~ 15 要?來標(biāo)記出現(xiàn)?常頻繁的字段,要為將來有可能添加的、頻繁出現(xiàn)的字段預(yù)留?些出來。
上面我們定義完 message ,相面 就來編譯 contacts.proto 文件 來生成 Java 文件
這里 編譯的方法有兩種
- 使用 命令行編譯
- 使用 maven插件編譯
這里先來看定義中 : 使用命令行編譯
?
編譯命令行格式為 :
protoc [--proto_path=IMPORT_PATH] --java_out=DST_DIR path/to/file.protoprotoc 是 Protocol Buffer 提供的命令?編譯?具。--proto_path 指定 被編譯的.proto?件所在?錄,可多次指定??珊唽懗?-I IMPORT_PATH 。如不指定該參數(shù),則在當(dāng)前?錄進(jìn)?搜索。當(dāng)某個(gè).proto ?件 import 其他.proto ?件時(shí), 或需要編譯的 .proto ?件不在當(dāng)前?錄下,這時(shí)就要?-I來指定搜索?錄。--java_out= 指編譯后的?件為 JAVA ?件。OUT_DIR 編譯后?成?件的?標(biāo)路徑。path/to/file.proto 要編譯的.proto?件。
?
學(xué)習(xí)完 命令行編譯 , 在來學(xué)習(xí)一下 使用 maven 插件 編譯 .
?
關(guān)于 這種編譯?式??動(dòng)執(zhí)? protoc 命令,后?跟?堆易忘的參數(shù)要高效省心得多(每次編譯都得google 或找之前記的筆記)。
?
使用 maven 插件 編譯 只需要在pom中添加 porotbuf 編譯插件:
<plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.6.1</version><configuration><!-- 本地安裝的protoc.exe的?錄 --><protocExecutable>D:\JavaSE練習(xí)\protobuf\protoc-21.11-win64\bin\protoc.exe</protocExecutable><!-- proto?件放置的?錄,默認(rèn)為/src/main/proto --><protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot><!-- ?成?件的?錄,默認(rèn)?成到target/generated-sources/protobuf/ --><outputDirectory>${project.basedir}/src/main/java</outputDirectory><!-- 是否清空?標(biāo)?錄,默認(rèn)值為true。這個(gè)最好設(shè)置為false,以免誤刪項(xiàng)??件!!! --><clearOutputDirectory>false</clearOutputDirectory></configuration></plugin>
?
補(bǔ)充 : 使用這個(gè)插件是有坑的
- 使用這個(gè)插件 項(xiàng)目路徑是不能帶中文的
?
關(guān)于兩種編譯方式就看完了, 下面我們了解一下 編譯生成的 java 文件 和 內(nèi)容.
?
圖一 :
?
圖二 :
?
簡單了解完 編譯器生成的代碼, 接下來就來到大家感興趣的部分,開始寫代碼了.
?
到此我們就完成了下面三點(diǎn).
- 對?個(gè)聯(lián)系?的信息使? PB 進(jìn)?序列化,并將結(jié)果打印出來。
- 對序列化后的內(nèi)容使? PB 進(jìn)?反序列,解析出聯(lián)系信息并打印出來。
- 聯(lián)系?包含以下信息: 姓名、年齡。
?
簡單快速上手 ProtoBuf , 下面我們來學(xué)習(xí) Proto3 語法
?
4. proto3 語法
?
關(guān)于 Proto3 語法的學(xué)習(xí),通過完成下面 幾點(diǎn)需求 來學(xué)習(xí).
- 不再打印聯(lián)系?的序列化結(jié)果,?是將通訊錄序列化后并寫??件中。
- 從?件中將通訊錄解析出來,并進(jìn)?打印。
- 新增聯(lián)系?屬性,共包括:姓名、年齡、電話信息、地址、其他聯(lián)系?式、備注。
?
1.字段規(guī)則
?
消息的字段可以?下??種規(guī)則來修飾:
? singular
:消息中可以包含該字段零次或?次(不超過?次) ,proto3 語法中,字段默認(rèn)使?該 規(guī)則。
? repeated
:消息中可以包含該字段任意多次(包括零次),其中重復(fù)值的順序會(huì)被保留??梢岳?解為定義了?個(gè)數(shù)組。
?
演示 :
?
2. 消息類型的定義與使用
?
定義 : 在單個(gè) .proto ?件
中可以定義多個(gè)消息體,且?持定義嵌套類型的消息(任意多層)。每個(gè)消息體中 的字段編號可以重復(fù)。
?
圖一 :
?
圖二 :
?
看完上面兩個(gè)語法點(diǎn)我們就可以來完成三個(gè)需求了 :
?
圖一 :
?
圖二 :
?
附上代碼 :
package com.example.proto3;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder = Contacts.newBuilder();// // 讀取本地已存在的 contacts.bin 反序列化出 通訊錄對象
// Contacts contacts = Contacts.parseFrom(new FileInputStream(
// "src/main/java/com/example/proto3/contacts.bin"
// ));
//
// // 這里向統(tǒng)通訊錄中新增一個(gè)聯(lián)系人 需要獲取到 builder .
// contactsBuilder = contacts.toBuilder();try {contactsBuilder.mergeFrom(new FileInputStream("src/main/java/com/example/proto3/contacts.bin"));} catch (FileNotFoundException e) {System.out.println("contacts.bin not find , create new file");}// 向通訊錄中新增一個(gè)聯(lián)系人contactsBuilder.addContacts(addPeopleInfo());// 序列化通訊錄, 將結(jié)果寫入文件中FileOutputStream outputStream = new FileOutputStream("src/main/java/com/example/proto3/contacts.bin");// writeTo 方法會(huì)完成兩部操作 1. 序列化 聯(lián)系熱 2. 將序列化的結(jié)果添加到 文件中contactsBuilder.build().writeTo(outputStream);}private static PeopleInfo addPeopleInfo() {PeopleInfo.Builder builder = PeopleInfo.newBuilder();Scanner sc = new Scanner(System.in);System.out.println("------------ 新增聯(lián)系人 --------------");System.out.print("請輸入聯(lián)系人姓名: ");String name = sc.nextLine();builder.setName(name);System.out.print("請輸入聯(lián)系人年齡: ");int age = sc.nextInt();// 用戶輸入完數(shù)字后會(huì)有一個(gè)回車 這里需要使用 nextLine() 將回車讀出來sc.nextLine();builder.setAge(age);// 設(shè)置聯(lián)系人的電話信息for (int i = 0; ; i++) {// 這里 寫一個(gè)死循環(huán) ,讓用戶一直輸出 電話信息System.out.print("請輸入聯(lián)系人電話" + (i + 1) + "(只輸入回車完成電話新增): ");String number = sc.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);builder.addPhone(phoneBuilder);}System.out.println("------------ 添加聯(lián)系人介紹 ------------");// 通過 build 方法返回一個(gè) peopleInforeturn builder.build();}
}
?
讀取 contacts.bin 文件 ,進(jìn)行反序列操作
?
到此前兩個(gè) 需求就完成了,下面我們繼續(xù)來了解 proto3的語法點(diǎn).
?
3. enum類型
?
圖一 :
?
圖二 :
?
簡單學(xué)習(xí)完 枚舉類型 ,下面就來通過 枚舉 類型 來完成我們的需求三 :
- 新增聯(lián)系?屬性,共 包括:姓名、年齡、電話信息、地址、其他聯(lián)系?式、備注。
枚舉類型學(xué)完,下面繼續(xù) 學(xué)習(xí) proto3的語法
?
4. Any類型
?
Any 其實(shí)是一個(gè) 消息類型, 如 : message Any , Any 是ProtoBuf 為我們定義好了的消息類型.
?
引用
字段還可以聲明為 Any 類型,可以理解為泛型類型。使?時(shí)可以在 Any 中存儲(chǔ)任意消息類型。
?
Any 類 型的字段也? repeated 來修飾。 Any 類型是 google 已經(jīng)幫我們定義好的類型,在安裝 ProtoBuf 時(shí),其中的 include ?錄下查找
?
所有 google 已經(jīng)定義好的 .proto ?件。
?
?
下面來使用一下 Any 類型 :
?
圖一 :
?
圖二 :
?
圖三 :
?
附上代碼 :
?
TestWrite
package com.example.proto3;import com.google.protobuf.Any;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder = Contacts.newBuilder();// // 讀取本地已存在的 contacts.bin 反序列化出 通訊錄對象
// Contacts contacts = Contacts.parseFrom(new FileInputStream(
// "src/main/java/com/example/proto3/contacts.bin"
// ));
//
// // 這里向統(tǒng)通訊錄中新增一個(gè)聯(lián)系人 需要獲取到 builder .
// contactsBuilder = contacts.toBuilder();try {contactsBuilder.mergeFrom(new FileInputStream("src/main/java/com/example/proto3/contacts.bin"));} catch (FileNotFoundException e) {System.out.println("contacts.bin not find , create new file");}// 向通訊錄中新增一個(gè)聯(lián)系人contactsBuilder.addContacts(addPeopleInfo());// 序列化通訊錄, 將結(jié)果寫入文件中FileOutputStream outputStream = new FileOutputStream("src/main/java/com/example/proto3/contacts.bin");// writeTo 方法會(huì)完成兩部操作 1. 序列化 聯(lián)系熱 2. 將序列化的結(jié)果添加到 文件中contactsBuilder.build().writeTo(outputStream);// 最后別忘記關(guān)閉流對象outputStream.close();}private static PeopleInfo addPeopleInfo() {PeopleInfo.Builder builder = PeopleInfo.newBuilder();Scanner sc = new Scanner(System.in);System.out.println("------------ 新增聯(lián)系人 --------------");System.out.print("請輸入聯(lián)系人姓名: ");String name = sc.nextLine();builder.setName(name);System.out.print("請輸入聯(lián)系人年齡: ");int age = sc.nextInt();// 用戶輸入完數(shù)字后會(huì)有一個(gè)回車 這里需要使用 nextLine() 將回車讀出來sc.nextLine();builder.setAge(age);// 設(shè)置聯(lián)系人的電話信息for (int i = 0; ; i++) {// 這里 寫一個(gè)死循環(huán) ,讓用戶一直輸出 電話信息System.out.print("請輸入聯(lián)系人電話" + (i + 1) + "(只輸入回車完成電話新增): ");String number = sc.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);System.out.println("請輸入此電話類型(1. 移動(dòng)電話 2. 固定電話 )");int type = sc.nextInt();// 接收回車sc.nextLine();switch (type) {case 1:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.MP);break;case 2:phoneBuilder.setType(PeopleInfo.Phone.PhoneType.TEL);break;default:System.out.println("選擇錯(cuò)誤!");}builder.addPhone(phoneBuilder);}// 設(shè)置聯(lián)系人的 地址信息Address.Builder addressBuilder = Address.newBuilder();System.out.print("請輸入聯(lián)系人的家庭地址: ");String homeAddress = sc.nextLine();addressBuilder.setHomeAddress(homeAddress);System.out.print("請輸入聯(lián)系人的單位地址: ");String unitAddress = sc.nextLine();addressBuilder.setUnitAddress(unitAddress);builder.setData(Any.pack(addressBuilder.build()));System.out.println("------------ 添加聯(lián)系人介紹 ------------");// 通過 build 方法返回一個(gè) peopleInforeturn builder.build();}
}
?
TestRead
package com.example.proto3;import com.google.protobuf.InvalidProtocolBufferException;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 讀取文件, 將讀取的內(nèi)容進(jìn)行反序列化Contacts contacts = Contacts.parseFrom(new FileInputStream("src/main/java/com/example/proto3/contacts.bin"));// 打印printContacts(contacts);
// System.out.println(contacts.toString());}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {int i = 1;// 通過 getContactsList() 方法 , 獲取到每個(gè) peopleInfofor (PeopleInfo peopleInfo : contacts.getContactsList()) {System.out.println("---------- 聯(lián)系人 " + i++ + "-----------------");System.out.println("姓名: " + peopleInfo.getName());System.out.println("年齡: " + peopleInfo.getAge());int j = 1;// 聯(lián)系人的電話信息是 存在一個(gè)數(shù)組里面這里就需要 遍歷打印for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println("電話" + j++ + ": " + phone.getNumber()+ " (" + phone.getType().name() + ")");}// 通過 hasData 方法 判斷 Data 中是否存放了數(shù)據(jù) , 通過 is 方法 判斷 Data 中存放的是否為 Addressif (peopleInfo.hasData() && peopleInfo.getData().is(Address.class)) {// 此時(shí)說明 有數(shù)據(jù) , 并為 Address// 在打印之前需要轉(zhuǎn)化一下Address address = peopleInfo.getData().unpack(Address.class);if (!address.getHomeAddress().isEmpty()) {System.out.println("家庭地址: " + address.getHomeAddress());}if (!address.getUnitAddress().isEmpty()) {System.out.println("單位地址: " + address.getUnitAddress());}}}}
}
?
5. oneof 類型
?
到目前為止 ,我們的通訊錄 已經(jīng)可以添加 姓名 ,年齡 , 電話信息 , 地址信息 , 下面通過 oneof 類型 再給通訊錄 添加一個(gè) 字段 如 : 其他聯(lián)系方式 (qq , wechat)
?
圖一 :
?
圖二 :
?
6. map類型
?
這里我們學(xué)習(xí) map 類型 ,同樣 通過 升級 通訊錄來學(xué)習(xí), 這次我們向聯(lián)系人中添加備注信息.
?
圖一 :
?
圖二 :
?
到此 關(guān)于 protobuf 的 大部分類型我們都看過了 ,想必大家對使用 protobuf 沒啥問題了,下面來說說 使用 protobuf 的一些坑.
?
7. 默認(rèn)值
?
反序列化消息時(shí),如果被反序列化的?進(jìn)制序列中不包含某個(gè)字段,反序列化對象中相應(yīng)字段時(shí),就 會(huì)設(shè)置為該字段的默認(rèn)值。不同的類型對應(yīng)的默認(rèn)值不同:
? 對于字符串,默認(rèn)值為空字符串。
?
? 對于字節(jié),默認(rèn)值為空字節(jié)。
?
? 對于布爾值,默認(rèn)值為 false。
?
? 對于數(shù)值類型,默認(rèn)值為 0。
?
? 對于枚舉,默認(rèn)值是第?個(gè)定義的枚舉值, 必須為 0。
?
? 對于消息字段,未設(shè)置該字段。它的取值是依賴于語?。
?
? 對于設(shè)置了 repeated 的字段的默認(rèn)值是空的( 通常是相應(yīng)語?的?個(gè)空列表 )。
?
? 對于 消息字段 、 oneof字段 和 any字段 , 都有 has ?法來檢測當(dāng)前字段是否被設(shè)置。
?
? 對于 標(biāo)量數(shù)據(jù)類型 沒有 has 方法
?
舉例 :
?
?
8. 更新消息
?
這里來說說 如果我們要更新消息類型需要注意那些點(diǎn) :
?
如果現(xiàn)有的消息類型已經(jīng)不再滿?我們的需求,例如需要擴(kuò)展?個(gè)字段,在不破壞任何現(xiàn)有代碼的情 況下更新消息類型?常簡單。
?
關(guān)于 更新規(guī)則 : 這里說兩點(diǎn)
- 新增字段 : 注意新增的字段不和老字段沖突 :
名稱 字段號
- 修改老字段 :
- 禁?修改任何已有字段的字段編號。
?- int32, uint32, int64, uint64 和 bool 是完全兼容的??梢詮倪@些類型中的?個(gè)改為另?個(gè), ?不破壞前后兼容性。若解析出來的數(shù)值與相應(yīng)的類型不匹配,可能會(huì)被截?cái)?#xff08;例如,若將 64 位整數(shù)當(dāng)做 32 位進(jìn)?讀取,它將被截?cái)酁?32 位)。
?
?
sint32 和 sint64 相互兼容但不與其他的整型兼容。
?string 和 bytes 在合法 UTF-8 字節(jié)前提下也是兼容的。
?bytes 包含消息編碼版本的情況下,嵌套消息與 bytes 也是兼容的。
?fixed32 與 sfixed32 兼容, fixed64 與 sfixed64兼容。 ? enum 與 int32,uint32, int64 和 uint64 兼容(注意若值不匹配會(huì)被截?cái)?#xff09;。但要注意當(dāng)反序 列化消息時(shí)會(huì)根據(jù)語?采?不同的處理?案:例如,未識別的 proto3 枚舉類型會(huì)被保存在消息 中,但是當(dāng)消息反序列化時(shí)如何表?是依賴于編程語?的。整型字段總是會(huì)保持其的值。
?oneof:
將?個(gè)單獨(dú)的值更改為 新 oneof 類型成員之?是安全和?進(jìn)制兼容的。
若確定沒有代碼?次性設(shè)置多個(gè)值那么將多個(gè)字段移??個(gè)新 oneof 類型也是可?的。
將任何字段移?已存在的 oneof 類型是不安全的。
?
補(bǔ)充:若是移除?字段,要保證不再使?移除字段的字段編號。正確的做法是保留字段編號 (reserved),以確保該編號將不能被重復(fù)使?。不建議直接刪除或注釋掉字段。
?
演示 : 刪除老字段后 ,定義新字段 使用老子段的編號出現(xiàn)的問題 .
?
圖一 :
TestRead :
package com.example.update.service;import com.google.protobuf.InvalidProtocolBufferException;import java.io.FileInputStream;
import java.io.IOException;public class TestRead {public static void main(String[] args) throws IOException {// 讀取文件, 將讀取的內(nèi)容進(jìn)行反序列化Contacts contacts = Contacts.parseFrom(new FileInputStream("src/main/java/com/example/proto3/contacts2.bin"));// 打印printContacts(contacts);
// System.out.println(contacts.toString());}private static void printContacts(Contacts contacts) throws InvalidProtocolBufferException {int i = 1;// 通過 getContactsList() 方法 , 獲取到每個(gè) peopleInfofor (PeopleInfo peopleInfo : contacts.getContactsList()) {System.out.println("---------- 聯(lián)系人 " + i++ + "-----------------");System.out.println("姓名: " + peopleInfo.getName());System.out.println("年齡: " + peopleInfo.getAge());int j = 1;// 聯(lián)系人的電話信息是 存在一個(gè)數(shù)組里面這里就需要 遍歷打印for (PeopleInfo.Phone phone : peopleInfo.getPhoneList()) {System.out.println("電話" + j++ + ": " + phone.getNumber());}}}
}
?
TestWrite
package com.example.update.service;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;public class TestWrite {public static void main(String[] args) throws IOException {Contacts.Builder contactsBuilder = Contacts.newBuilder();try {contactsBuilder.mergeFrom(new FileInputStream("src/main/java/com/example/proto3/contacts2.bin"));} catch (FileNotFoundException e) {System.out.println("contacts.bin not find , create new file");}// 向通訊錄中新增一個(gè)聯(lián)系人contactsBuilder.addContacts(addPeopleInfo());// 序列化通訊錄, 將結(jié)果寫入文件中FileOutputStream outputStream = new FileOutputStream("src/main/java/com/example/proto3/contacts2.bin");// writeTo 方法會(huì)完成兩部操作 1. 序列化 聯(lián)系熱 2. 將序列化的結(jié)果添加到 文件中contactsBuilder.build().writeTo(outputStream);// 最后別忘記關(guān)閉流對象outputStream.close();}private static PeopleInfo addPeopleInfo() {PeopleInfo.Builder builder = PeopleInfo.newBuilder();Scanner sc = new Scanner(System.in);System.out.println("------------ 新增聯(lián)系人 --------------");System.out.print("請輸入聯(lián)系人姓名: ");String name = sc.nextLine();builder.setName(name);System.out.print("請輸入聯(lián)系人年齡: ");int age = sc.nextInt();// 用戶輸入完數(shù)字后會(huì)有一個(gè)回車 這里需要使用 nextLine() 將回車讀出來sc.nextLine();builder.setAge(age);// 設(shè)置聯(lián)系人的電話信息for (int i = 0; ; i++) {// 這里 寫一個(gè)死循環(huán) ,讓用戶一直輸出 電話信息System.out.print("請輸入聯(lián)系人電話" + (i + 1) + "(只輸入回車完成電話新增): ");String number = sc.nextLine();if (number.isEmpty()) {break;}PeopleInfo.Phone.Builder phoneBuilder = PeopleInfo.Phone.newBuilder();phoneBuilder.setNumber(number);builder.addPhone(phoneBuilder);}System.out.println("------------ 添加聯(lián)系人介紹 ------------");// 通過 build 方法返回一個(gè) peopleInforeturn builder.build();}
}
?
這兩個(gè)類 相比之前 減少了一些 添加字段的操作.
?
看完上面知道了 要避免使用移除后老字段的編號 ,如果 字段非常多 ,編號也分常多 ,總免有忘記刪除字段后使用過的編號
?
這里想要避免這種情況 就可以使用一個(gè) 關(guān)鍵字 : reserved
, proto3提供的關(guān)鍵字.
?
演示 :
?
9. 未知字段
?
?
10. 前后兼容性
?
在 更新字段 規(guī)則中提到過 前后兼容性問題 ,這里 就來 具體的了解一下.
?
根據(jù)上述的例?可以得出,pb是具有向前兼容的。為了敘述?便,把增加了“??”屬性的 service 稱為“新模塊”;未做變動(dòng)的 client 稱為 “?模塊”。
? 向前兼容:?模塊能夠正確識別新模塊?成或發(fā)出的協(xié)議。這時(shí)新增加的“??”屬性會(huì)被當(dāng)作未 知字段(pb 3.5版本及之后)。
? 向后兼容:新模塊也能夠正確識別?模塊?成或發(fā)出的協(xié)議。 前后兼容的作?:當(dāng)我們維護(hù)?個(gè)很龐?的分布式系統(tǒng)時(shí),由于你?法同時(shí) 升級所有 模塊,為了保證 在升級過程中,整個(gè)系統(tǒng)能夠盡可能不受影響,就需要盡量保證通訊協(xié)議的“向后兼容”或“向前兼 容”。
?
前后兼容的作?:當(dāng)我們維護(hù)?個(gè)很龐?的分布式系統(tǒng)時(shí),由于你?法同時(shí) 升級所有 模塊,為了保證 在升級過程中,整個(gè)系統(tǒng)能夠盡可能不受影響,就需要盡量保證通訊協(xié)議的“向后兼容”或“向前兼容”。
?
11. option 選項(xiàng)
?
.proto 文件中可以聲明許多選項(xiàng),使用 option 標(biāo)注。選項(xiàng)能影響 proto 編譯器的某些處理?式。
?
關(guān)于 option 能選著的選項(xiàng) 可以在 google/protobuf/descriptor.proto
中查看.
?
這里來說一說關(guān)于 java 常用的選項(xiàng)
- java_multiple_files:編譯后?成的?件是否分為多個(gè)?件,該選項(xiàng)為?件選項(xiàng)。
- java_package:編譯后?成?件所在的包路徑,該選項(xiàng)為?件選項(xiàng)。
- java_outer_classname:編譯后?成的proto包裝類的類名,該選項(xiàng)為?件選項(xiàng)。
- allow_alias : 允許將相同的常量值分配給不同的枚舉常量,?來定義別名。該選項(xiàng)為枚舉選項(xiàng)。
?
前面三個(gè) 文件級別的選項(xiàng)我們已經(jīng) 使用過 ,這里不多說 這里 主要看看 allow_alias
選項(xiàng)。
?
演示 :
?
除了 列舉好了的選項(xiàng) 我們 還可以自定義選項(xiàng) , 但是 關(guān)于自定義選項(xiàng) 大部分場景是用不到的,別人定義好的就足夠用了, 這里有興趣 可以自己查看
:Language Guide (proto 2) | Protocol Buffers Documentation (protobuf.dev) 這個(gè)網(wǎng)站進(jìn)行學(xué)習(xí).
?
5. probuf 實(shí)戰(zhàn)
?
到此 我們 已經(jīng)對 proto3 語法有了一定了解 ,但是 光了解 肯定還是不行的,下面我們來簡單進(jìn)行一個(gè)實(shí)戰(zhàn) ,對 通訊錄 進(jìn)行最后一次升級 ,實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)版本的通訊錄.
?
既然是網(wǎng)絡(luò)版本的肯定是存在 客戶端 , 服務(wù)器 .
?
這里來看看完成這個(gè)實(shí)戰(zhàn)的 需求 :
?
? 客?端:向服務(wù)端發(fā)送聯(lián)系?信息,并接收服務(wù)端返回的響應(yīng)。
? 服務(wù)端:接收到聯(lián)系?信息后,將結(jié)果打印出來。
? 客?端、服務(wù)端間的交互數(shù)據(jù)使? Protobuf 來完成。
?
流程圖 :
?
這里 使用 maven + upd 數(shù)據(jù)報(bào)套接字 進(jìn)行 編程.
?
1.搭建客戶端服務(wù)端
這里就是網(wǎng)絡(luò)編程 使用 socket 套接字 , 如果 忘記或不清楚的話 可以看看 這篇文章 :網(wǎng)絡(luò)編程 – socket 套接字_牧…的博客-CSDN博客
?
客戶端 :
package com.example.internet.client;import com.example.proto3.Address;
import com.example.proto3.PeopleInfo;import java.io.IOException;
import java.net.*;public class ContactsClient {// 這里將 客戶端的 端口號 和 IP 地址寫死 .private static SocketAddress ADDRESS = new InetSocketAddress("localhost", 8888);public static void main(String[] args) throws IOException {// 創(chuàng)建客戶端 DatagramSocketDatagramSocket socket = new DatagramSocket();// 構(gòu)造 request 請求byte[] requestData = {'h', 'e', 'l', 'l', '0'};DatagramPacket requestPacket = new DatagramPacket(requestData, requestData.length, ADDRESS);// 發(fā)送 request 數(shù)據(jù)報(bào)socket.send(requestPacket);System.out.println("發(fā)送成功!");// 獲取 響應(yīng) (response)// 創(chuàng)建 response 數(shù)據(jù)報(bào) 用于接收服務(wù)端返回的響應(yīng)byte[] udpResponse = new byte[1024];DatagramPacket responsePacket = new DatagramPacket(udpResponse, udpResponse.length);// 接收 response 數(shù)據(jù)報(bào)socket.receive(responsePacket);int length = BytesUtils.getValidLength(udpResponse);byte[] responseData = BytesUtils.subByte(udpResponse, 0, length);// 打印結(jié)果System.out.printf("接收到響應(yīng) : %s" ,new String(responseData));}
}
?
BytesUtil
package com.example.internet.client;public class BytesUtils {// 獲取 bytes 的有效長度public static int getValidLength(byte[] bytes) {int i = 0;if (null == bytes || 0 == bytes.length) {return i;}for (; i < bytes.length; i++) {if (bytes[i] == '\0') {break;}}return i;}// 截?cái)郻ytespublic static byte[] subByte(byte[] b, int off, int length) {byte[] b1 = new byte[length];// 通過 arrayCopy 進(jìn)行截?cái)?(想到與 拷貝 length 長度到新的 數(shù)組中并返回)System.arraycopy(b, off, b1, 0, length);return b1;}
}
?
服務(wù)端 :
package com.example.internet.service;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;public class ContactsService {//服務(wù)器socket要綁定固定的端?private static final int PORT = 8888;public static void main(String[] args) throws IOException {// 創(chuàng)建 服務(wù)端 DatagramSock 指定端口號 , 發(fā)送及接收 UPD 數(shù)據(jù)報(bào)DatagramSocket socket = new DatagramSocket(PORT);// 寫一個(gè)死循環(huán) , 用于不停接收 客戶端發(fā)送的請求while (true) {System.out.println("等待接收 UDP 數(shù)據(jù)報(bào) ...");// 創(chuàng)建 request 數(shù)據(jù)報(bào) 用于接客戶端發(fā)送來的數(shù)據(jù)byte[] udpRequest = new byte[1024];DatagramPacket requestPacket = new DatagramPacket(udpRequest, udpRequest.length);// 接收 request 數(shù)據(jù)報(bào) , 在接收到數(shù)據(jù)報(bào)之前會(huì)一直阻塞socket.receive(requestPacket);// 獲取有效的 requestint length = BytesUtils.getValidLength(udpRequest);byte[] requestData = BytesUtils.subByte(udpRequest, 0, length);System.out.println("接收到請求: " + new String(requestData));// 構(gòu)造 響應(yīng)返回給客戶端byte[] responseData = {'s', 'u', 'c', 'c', 'e', 's', 's'};// 構(gòu)造 response 數(shù)據(jù)報(bào)DatagramPacket responsePacket = new DatagramPacket(responseData, responseData.length,requestPacket.getSocketAddress());// 發(fā)送 response 數(shù)據(jù)報(bào)socket.send(responsePacket);System.out.println("發(fā)送成功!");} }
}
?
效果 :
?
到此 客戶端 和 服務(wù)端就構(gòu)造好了,下面 就來用上我們的 protobuf 進(jìn)行 序列化 和 反序列化操作.
?
圖一 :
?
圖二 :
6. 總結(jié)
?
到此關(guān)于 protobuf 的學(xué)習(xí)就完成了, 簡單學(xué)習(xí)了一下語法 ,簡單使用了一些 api .
?
這里來通過代碼的形式來對比 驗(yàn)證 JSON 和 PB 的能力.
?
1. 序列化能力對比
?
在這?讓我們分別使? PB 與 JSON 的序列化與反序列化能?, 對值完全相同的?份結(jié)構(gòu)化數(shù)據(jù)進(jìn)? 不同次數(shù)的性能測試。
為了可讀性,下?這?份?本使? JSON 格式展?了需要被進(jìn)?測試的結(jié)構(gòu)化數(shù)據(jù)內(nèi)容:
{"age": 20,"name": "張珊","phone": [{"number": "110112119","type": 0},{"number": "110112119","type": 0},{"number": "110112119","type": 0},{"number": "110112119","type": 0},{"number": "110112119","type": 0}],"qq": "95991122","address": {"home_address": "陜西省西安市?安區(qū)","unit_address": "陜西省西安市雁塔區(qū)"},"remark": {"key1": "value1","key2": "value2","key3": "value3","key4": "value4","key5": "value5"}
}
?
提供測試用的代碼 :
package com.example.compare;import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;import static com.example.compare.PeopleInfoForJson.Phone.PhoneType.MP;public class Compare {private static int TEST_COUNT = 100000;public static void main(String[] args) throwsInvalidProtocolBufferException, JsonProcessingException {int count = 0;byte[] pbBytes = new byte[0];String jsonStr = null;ObjectMapper objectMapper = new ObjectMapper();// ------------------------------Protobuf 序列化 -----------------------------------{PeopleInfo peopleInfo = buildPeopleInfo();count = TEST_COUNT;long stime = System.currentTimeMillis();// 序列化count次while ((count--) > 0) {pbBytes = peopleInfo.toByteArray();}long etime = System.currentTimeMillis();System.out.printf("%d次 [pb序列化]耗時(shí):%dms, 序列化后的??: %d\n",TEST_COUNT, etime - stime, pbBytes.length);}// ------------------------------Protobuf 反序列化 ---------------------------------{count = TEST_COUNT;long stime = System.currentTimeMillis();// 反序列化count次while ((count--) > 0) {PeopleInfo.parseFrom(pbBytes);}long etime = System.currentTimeMillis();System.out.printf("%d次 [pb反序列化]耗時(shí):%dms\n",TEST_COUNT, etime - stime);}// ---------------------------- fastjson2 序列化 ------------------------------------{PeopleInfoForJson peopleInfoForJson = buildPeopleInfoForJson();count = TEST_COUNT;long stime = System.currentTimeMillis();// 序列化count次while ((count--) > 0) {jsonStr = JSON.toJSONString(peopleInfoForJson);//JSON.toJSONString(peopleInfoForJson);}long etime = System.currentTimeMillis();System.out.printf("%d次 [fastjson2序列化]耗時(shí):%dms, 序列化后的??: % d\n",TEST_COUNT, etime - stime, jsonStr.length());}// --------------------------- fastjson2 反序列化 -----------------------------------{count = TEST_COUNT;long stime = System.currentTimeMillis();// 反序列化count次while ((count--) > 0) {JSON.parseObject(jsonStr, PeopleInfoForJson.class);}long etime = System.currentTimeMillis();System.out.printf("%d次 [fastjson2反序列化]耗時(shí):%dms\n",TEST_COUNT, etime - stime);}// ------------------------------jackson 序列化 ---------------------------------------{PeopleInfoForJson peopleInfoForJson = buildPeopleInfoForJson();count = TEST_COUNT;long stime = System.currentTimeMillis();// 序列化count次while ((count--) > 0) {jsonStr = objectMapper.writeValueAsString(peopleInfoForJson);}long etime = System.currentTimeMillis();System.out.printf("%d次 [jackson序列化]耗時(shí):%dms, 序列化后的??: % d\n",TEST_COUNT, etime - stime, jsonStr.length());}// ------------------------------jackson 反序列化 -------------------------------------{count = TEST_COUNT;long stime = System.currentTimeMillis();// 反序列化count次while ((count--) > 0) {objectMapper.readValue(jsonStr, PeopleInfoForJson.class);}long etime = System.currentTimeMillis();System.out.printf("%d次 [jackson反序列化]耗時(shí):%dms\n",TEST_COUNT, etime - stime);}}private static PeopleInfo buildPeopleInfo() {PeopleInfo.Builder peopleBuilder = PeopleInfo.newBuilder();peopleBuilder.setName("張珊");peopleBuilder.setAge(20);peopleBuilder.setQq("95991122");for (int i = 0; i < 5; i++) {PeopleInfo.Phone.Builder phoneBuild =PeopleInfo.Phone.newBuilder();phoneBuild.setNumber("110112119");phoneBuild.setType(PeopleInfo.Phone.PhoneType.MP);}com.example.proto3.Address.Builder addressBuilder =com.example.proto3.Address.newBuilder();addressBuilder.setHomeAddress("陜西省西安市?安區(qū)");addressBuilder.setUnitAddress("陜西省西安市雁塔區(qū)");peopleBuilder.setData(Any.pack(addressBuilder.build()));peopleBuilder.putRemark("key1", "value1");peopleBuilder.putRemark("key2", "value2");peopleBuilder.putRemark("key3", "value3");peopleBuilder.putRemark("key4", "value4");peopleBuilder.putRemark("key5", "value5");return peopleBuilder.build();}private static PeopleInfoForJson buildPeopleInfoForJson() {PeopleInfoForJson peopleInfo = new PeopleInfoForJson();peopleInfo.setName("張珊");peopleInfo.setAge(20);peopleInfo.setQq("95991122");for (int i = 0; i < 5; i++) {PeopleInfoForJson.Phone phone = new PeopleInfoForJson.Phone();phone.setNumber("110112119");phone.setType(MP);peopleInfo.getPhones().add(phone);}PeopleInfoForJson.Address address = new PeopleInfoForJson.Address();address.setHomeAddress("陜西省西安市?安區(qū)");address.setUnitAddress("陜西省西安市雁塔區(qū)");peopleInfo.setAddress(address);peopleInfo.getRemark().put("key1", "value1");peopleInfo.getRemark().put("key2", "value2");peopleInfo.getRemark().put("key3", "value3");peopleInfo.getRemark().put("key4", "value4");peopleInfo.getRemark().put("key5", "value5");return peopleInfo;}
}
?
分別對相同的結(jié)構(gòu)化數(shù)據(jù)進(jìn)? 100 、 1000 、 10000 、 100000 次的序列化與反序列化,包含:PB、fastjson2、jackson,分別獲取其耗時(shí)與序列化后的 ??。
完整代碼 :
總結(jié)
序列化協(xié)議 | 通用性 | 格式 | 可讀性 | 序列化大小 | 序列化性能 | 適?場景 |
---|---|---|---|---|---|---|
JSON | 通? (json、 xml已成為多種 ?業(yè)標(biāo)準(zhǔn)的編 寫?具) | 文本格式 | 好 | 輕量 (使 ?鍵值對 ?式,壓 縮了?定 的數(shù)據(jù)空 間) | 中 | web項(xiàng)?。因?yàn)闉g覽 器對于json數(shù)據(jù)?持 ?常好,有很多內(nèi)建 的函數(shù)?持。 |
XML | 通? | ?本格式 | 好 | 重量(數(shù) 據(jù)冗余, 因?yàn)樾枰?成對的閉 合標(biāo)簽) | 低 | XML 作為?種擴(kuò)展標(biāo) 記語?,衍?出了 HTML、RDF/RDFS, 它強(qiáng)調(diào)數(shù)據(jù)結(jié)構(gòu)化的 能?和可讀性。 |
ProtoBuf | 獨(dú)? (Protobuf只 是Google公司 內(nèi)部的?具) | ?進(jìn)制格式 | 差(只能 反序列化 后得到真 正可讀的 數(shù)據(jù)) | 輕量(? JSON更輕 量,傳輸 起來帶寬 和速度會(huì) 有優(yōu)化) | ? | 適合?性能,對響應(yīng) 速度有要求的數(shù)據(jù)傳 輸場景。Protobuf? XML、JSON 更?、 更快。 |
?結(jié):
- XML、JSON、ProtoBuf 都具有數(shù)據(jù)結(jié)構(gòu)化和數(shù)據(jù)序列化的能?。
- XML、JSON 更注重?cái)?shù)據(jù)結(jié)構(gòu)化,關(guān)注可讀性和語義表達(dá)能?。ProtoBuf 更注重?cái)?shù)據(jù)序列化,關(guān)注 效率、空間、速度,可讀性差,語義表達(dá)能?不?,為保證極致的效率,會(huì)舍棄?部分元信息。
- ProtoBuf 的應(yīng)用場景更為明確 , XML , JSON 的應(yīng)用場景更為豐富.