偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

C++高性能序列化:ProtoBuf實(shí)現(xiàn)原理

開發(fā) 前端
什么是結(jié)構(gòu)化數(shù)據(jù)呢,正如字面上表達(dá)的,就是帶有一定結(jié)構(gòu)的數(shù)據(jù)。比如電話簿上有很多記錄數(shù)據(jù),每條記錄包含姓名、ID、郵件、電話等,這種結(jié)構(gòu)重復(fù)出現(xiàn)。

在當(dāng)今數(shù)據(jù)如洪流般奔涌的時代,高效的數(shù)據(jù)處理與傳輸對程序性能起著決定性作用。在C++開發(fā)領(lǐng)域,序列化作為將結(jié)構(gòu)化數(shù)據(jù)轉(zhuǎn)化為字節(jié)流的關(guān)鍵技術(shù),其效率高低直接影響著系統(tǒng)的整體表現(xiàn)。而Google開發(fā)的 Protocol Buffers(簡稱 ProtoBuf),作為一種高性能序列化框架,憑借其獨(dú)特優(yōu)勢,已成為眾多開發(fā)者的首選工具。

什么是結(jié)構(gòu)化數(shù)據(jù)呢,正如字面上表達(dá)的,就是帶有一定結(jié)構(gòu)的數(shù)據(jù)。比如電話簿上有很多記錄數(shù)據(jù),每條記錄包含姓名、ID、郵件、電話等,這種結(jié)構(gòu)重復(fù)出現(xiàn)。xml、json也可以用來存儲此類結(jié)構(gòu)化數(shù)據(jù),但是使用protobuf表示的數(shù)據(jù)能更加高效,并且將數(shù)據(jù)壓縮得更小,大約是json格式的1/10,xml格式的1/20。接下來,讓我們一同深入探究 ProtoBuf 在 C++ 中的實(shí)現(xiàn)原理,揭開其高性能背后的神秘面紗 。

Part1.protobuf簡介

1.1 protobuf安裝

先從github下載源代碼https://github.com/google/protobuf,源碼包中的src/README.md,有詳細(xì)的安裝說明,安裝過程如下:

  1. 解壓壓縮包:unzip protobuf-master.zip
  2. 進(jìn)入解壓后的文件夾:cd protobuf-master
  3. 安裝所需工具:sudo apt-get install autoconf automake libtool curl make g++ unzip
  4. 自動生成con?gure配置文件:./autogen.sh
  5. 配置環(huán)境:./con?gure
  6. 編譯源代碼(時間比較長):make
  7. 安裝:sudo make install
  8. 刷新動態(tài)庫:sudo ldcon?g

按步驟進(jìn)行即可完成安裝。安裝完成后可以使用 protoc --version 驗(yàn)證,安裝成功會輸出版本信息。

圖片圖片

1.2 Protobuf的特點(diǎn)

①高效性

在數(shù)據(jù)存儲和傳輸方面,Protobuf 展現(xiàn)出了卓越的高效性。與 XML 和 JSON 相比,Protobuf 序列化后的數(shù)據(jù)體積要小得多。這是因?yàn)?Protobuf 采用了二進(jìn)制編碼方式,摒棄了冗長的文本標(biāo)記 。例如,在傳輸一個包含用戶基本信息(如姓名、年齡、ID)的消息時,JSON 可能會因?yàn)槲谋靖袷胶玩I值對的描述方式而占用較大的空間,類似這樣:

{
  "name": "張三",
  "age": 30,
  "id": 123
}

而 XML 則會更加冗長,標(biāo)簽的重復(fù)使用會增加數(shù)據(jù)量:

<user>
  <name>張三</name>
  <age>30</age>
  <id>123</id>
</user>

但 Protobuf 通過緊湊的二進(jìn)制編碼,僅需少量字節(jié)就能表示相同的數(shù)據(jù),大大減少了存儲空間和網(wǎng)絡(luò)帶寬的占用。在解析速度上,Protobuf 也具有明顯優(yōu)勢。由于其解析過程基于生成的代碼直接操作二進(jìn)制數(shù)據(jù)流,避免了文本格式解析時的復(fù)雜字符串操作和格式檢查。在處理大規(guī)模數(shù)據(jù)時,Protobuf 的解析速度可比 JSON 快 5 到 10 倍 ,這使得它在對性能要求極高的場景中表現(xiàn)出色,如實(shí)時數(shù)據(jù)傳輸、大數(shù)據(jù)處理等領(lǐng)域。

②跨語言跨平臺

Protobuf 支持多種主流編程語言,包括 C++、Java、Python、Go、Ruby、C# 等 。這意味著無論你的項(xiàng)目是基于哪種語言開發(fā),都可以方便地引入 Protobuf 進(jìn)行數(shù)據(jù)處理。在一個大型分布式系統(tǒng)中,后端服務(wù)可能使用 C++ 編寫以獲得高性能,而前端應(yīng)用可能使用 JavaScript 與用戶交互,中間通過 Protobuf 定義的數(shù)據(jù)結(jié)構(gòu)進(jìn)行通信,實(shí)現(xiàn)了不同語言組件之間的無縫協(xié)作。

不僅如此,Protobuf 還具有跨平臺特性,無論是 Windows、Linux、macOS 等常見操作系統(tǒng),還是不同的硬件平臺,Protobuf 都能穩(wěn)定運(yùn)行。這種跨語言跨平臺的能力,使得它成為構(gòu)建分布式系統(tǒng)、微服務(wù)架構(gòu)等的理想選擇,開發(fā)者無需擔(dān)心因語言和平臺差異帶來的數(shù)據(jù)兼容性問題。

③擴(kuò)展性和兼容性

在實(shí)際應(yīng)用中,數(shù)據(jù)結(jié)構(gòu)往往不是一成不變的,隨著業(yè)務(wù)的發(fā)展和需求的變更,可能需要對數(shù)據(jù)結(jié)構(gòu)進(jìn)行修改和擴(kuò)展。Protobuf 在這方面表現(xiàn)出色,它允許在不破壞現(xiàn)有代碼的情況下更新消息類型。

當(dāng)在.proto文件中添加新字段時,只要保持字段編號的唯一性,舊版本的代碼仍然可以正確解析包含新字段的消息,只是會忽略新字段;而新版本的代碼則可以正常處理新舊兩種格式的消息,這就是所謂的向后兼容。同樣,當(dāng)刪除某個字段時,只要該字段的編號不再被使用,舊數(shù)據(jù)仍然可以被新版本的代碼解析,這體現(xiàn)了向前兼容。

例如,假設(shè)我們最初定義了一個簡單的User消息類型:

syntax = "proto3";

message User {
  string name = 1;
  int32 age = 2;
}

后來業(yè)務(wù)需求變更,需要添加一個email字段,我們可以這樣修改:

syntax = "proto3";

message User {
  string name = 1;
  int32 age = 2;
  string email = 3;
}

這樣,舊版本的應(yīng)用在解析新格式的數(shù)據(jù)時,雖然不知道email字段,但不會受到影響,依然能夠正確讀取name和age字段,保證了系統(tǒng)的穩(wěn)定性和兼容性 。

Part2.protobuf數(shù)據(jù)類型

protobuf的數(shù)據(jù)類型主要有三種:基本數(shù)據(jù)類型,復(fù)合類,Map類型(proto3版本后支持)。

2.1基本數(shù)據(jù)類型

  • double:雙精度浮點(diǎn)數(shù)。
  • float:單精度浮點(diǎn)數(shù)。
  • int32:32位有符號整數(shù)。
  • int64:64位有符號整數(shù)。
  • uint32:32位無符號整數(shù)。
  • uint64:64位無符號整數(shù)。
  • sint32:32位有符號整數(shù),使用zigzag編碼以優(yōu)化負(fù)數(shù)存儲。
  • sint64:64位有符號整數(shù),使用zigzag編碼以優(yōu)化負(fù)數(shù)存儲。
  • fixed32:32位無符號整數(shù)(固定長度,不使用varint編碼)。
  • fixed64:64位無符號整數(shù)(固定長度,不使用varint編碼)。
  • sfixed32:32位有符號整數(shù)(固定長度)。
  • sfixed64:64位有符號整數(shù)(固定長度)。
  • bool:布爾值,true 或 false。
  • string:UTF-8 編碼的字符串。
  • bytes:字節(jié)序列。

系統(tǒng)默認(rèn)值:

  • string:默認(rèn)為空字符串
  • byte:默認(rèn)值為空字節(jié)
  • bool:默認(rèn)為false
  • 數(shù)值:默認(rèn)為0
  • enum:默認(rèn)為第一個元素

示例如下:

syntax = "proto3";

//創(chuàng)建一個 SearchRequest 對象
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 results_per_page = 3;
}

2.2復(fù)雜數(shù)據(jù)類型

下面通過 Java數(shù)據(jù)類型來理解定義的 proto屬性。并引入 protobuf-java依賴:

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.19.1</version>
        </dependency>

(1)集合List字段

Java String、Integer List 在 protobuf 的定義。

message User{
  //list Int
  repeated int32 intList = 1;
  //list String
  repeated string strList = 2;
}

(2)Map字段

Java String、Integer Map 在 protobuf 的定義。

message User{
  // 定義簡單的 Map string
  map<string, int32> intMap = 7;
  // 定義復(fù)雜的 Map 對象
  map<string, string> stringMap = 8;
}

(3)對象字段

Java 對象 List 在 protobuf 的定義。

message User{
  //list 對象
  repeated Role roleList = 6;
}

(4)Map對象值字段

Java 對象 Map 在 protobuf 的定義。

message User{
  // 定義復(fù)雜的 Map 對象
  map<string, MapVauleObject> mapObject = 8;
}


// 定義 Map 的 value 對象
message MapVauleObject {
  string code = 1;
  string name = 2;
}

(5) 嵌套對象字段

Java 實(shí)體類中使用另一個實(shí)體類作為字段在 protobuf 的定義。

message User{
  // 對象
  NickName nickName = 4;
}

// 定義一個新的Name對象
message NickName {
  string nickName = 1;
}

Part3.Protobuf 的使用步驟

3.1定義數(shù)據(jù)結(jié)構(gòu)(.proto 文件)

使用 Protobuf 的第一步是定義數(shù)據(jù)結(jié)構(gòu),這通過創(chuàng)建一個.proto文件來完成。.proto文件使用一種特殊的語法來描述數(shù)據(jù)的結(jié)構(gòu),這種語法簡潔明了,易于理解和編寫。

下面是一個簡單的例子,假設(shè)我們要定義一個表示用戶信息的消息結(jié)構(gòu):

syntax = "proto3"; // 指定使用proto3語法

package user_info; // 定義包名,用于避免命名沖突

message User {
  string name = 1; // 字段類型 字段名 = 字段編號
  int32 age = 2;
  string email = 3;
}

在這個例子中:

  • syntax = "proto3";:聲明了使用的 Protobuf 語法版本,目前常用的是proto3,它相較于proto2有一些改進(jìn),如語法更簡潔、默認(rèn)字段規(guī)則的改變等 。
  • package user_info;:定義了包名user_info。包名在大型項(xiàng)目中非常重要,它可以防止不同模塊間消息類型的命名沖突,類似于編程語言中的命名空間。
  • message User:定義了一個名為User的消息類型,這是我們自定義的數(shù)據(jù)結(jié)構(gòu),類似于類的概念。
  • string name = 1;、int32 age = 2;、string email = 3;:這些是User消息中的字段定義。每個字段都有自己的數(shù)據(jù)類型(如string、int32)、字段名(如name、age、email)和唯一的字段編號(如1、2、3)。字段編號在消息的二進(jìn)制編碼中用于標(biāo)識字段,一旦確定就不應(yīng)輕易更改,因?yàn)檫@會影響到數(shù)據(jù)的兼容性 。

3.2生成代碼

定義好.proto文件后,就需要使用 Protobuf 編譯器(protoc)來生成對應(yīng)編程語言的代碼。protoc編譯器可以將.proto文件轉(zhuǎn)換為多種語言的源代碼,包括 C++、Java、Python 等 。

以 Python 為例,假設(shè)我們的.proto文件名為user.proto,并且已經(jīng)安裝好了protoc編譯器和 Python 的 Protobuf 庫。在命令行中執(zhí)行以下命令來生成 Python 代碼:

protoc --python_out=. user.proto

其中,--python_out=.表示將生成的 Python 代碼輸出到當(dāng)前目錄,user.proto是我們定義的數(shù)據(jù)結(jié)構(gòu)文件。執(zhí)行這個命令后,會在當(dāng)前目錄下生成一個user_pb2.py文件,這個文件包含了根據(jù).proto文件生成的 Python 類和函數(shù),用于對User消息進(jìn)行序列化和反序列化操作。

生成的代碼中包含了與.proto文件中定義的消息類型對應(yīng)的類,這些類具有一系列方法,如用于設(shè)置字段值的set_xxx方法、獲取字段值的get_xxx方法,以及用于序列化和反序列化的SerializeToString和ParseFromString方法等 。

使用生成的代碼進(jìn)行序列化和反序列化

生成代碼后,就可以在項(xiàng)目中使用這些代碼對數(shù)據(jù)進(jìn)行序列化和反序列化操作了。下面以 Python 為例,展示如何使用生成的代碼:

import user_pb2

# 創(chuàng)建一個User對象并賦值
user = user_pb2.User()
user.name = "李四"
user.age = 25
user.email = "lisi@example.com"

# 序列化
serialized_data = user.SerializeToString()
print("Serialized data:", serialized_data)

# 反序列化
new_user = user_pb2.User()
new_user.ParseFromString(serialized_data)
print("Deserialized user:")
print("Name:", new_user.name)
print("Age:", new_user.age)
print("Email:", new_user.email)

在這段代碼中:

  • 首先導(dǎo)入生成的user_pb2模塊,這個模塊包含了User類的定義。
  • 然后創(chuàng)建一個User對象,并設(shè)置其各個字段的值。
  • 使用user.SerializeToString()方法將User對象序列化為字節(jié)串,得到的serialized_data就是序列化后的數(shù)據(jù)。
  • 接著創(chuàng)建一個新的User對象new_user,使用new_user.ParseFromString(serialized_data)方法將字節(jié)串反序列化,恢復(fù)出原始的User對象的狀態(tài),并輸出其各個字段的值 。

Part4.Protobuf 的應(yīng)用場景

4.1分布式系統(tǒng)通信

在分布式系統(tǒng)中,不同組件之間需要進(jìn)行高效的數(shù)據(jù)交換和通信 。Protobuf 憑借其高效性和跨語言特性,成為了分布式系統(tǒng)通信的理想選擇。

以 Google 的分布式文件系統(tǒng)(GFS)為例,GFS 是一個大型的分布式文件系統(tǒng),用于存儲海量的數(shù)據(jù)。在 GFS 中,客戶端與服務(wù)器之間的通信就使用了 Protobuf。由于 GFS 需要處理大規(guī)模的數(shù)據(jù)存儲和讀取請求,對通信效率要求極高。Protobuf 的緊湊二進(jìn)制格式和快速解析速度,使得 GFS 能夠在高并發(fā)的情況下,快速地處理客戶端的請求,保證了系統(tǒng)的性能和穩(wěn)定性 。

再比如 Apache Dubbo,這是一個高性能的 Java RPC 框架,廣泛應(yīng)用于分布式服務(wù)架構(gòu)中。Dubbo 支持多種序列化方式,其中 Protobuf 就是一種重要的選擇。在 Dubbo 的服務(wù)調(diào)用過程中,使用 Protobuf 對請求和響應(yīng)數(shù)據(jù)進(jìn)行序列化和反序列化,可以顯著減少網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量,提高服務(wù)調(diào)用的效率,從而提升整個分布式系統(tǒng)的性能 。

4.2數(shù)據(jù)存儲和持久化

在數(shù)據(jù)存儲領(lǐng)域,Protobuf 同樣發(fā)揮著重要作用。當(dāng)數(shù)據(jù)需要持久化到磁盤或其他存儲介質(zhì)時,Protobuf 的緊湊二進(jìn)制格式能夠有效減少存儲空間的占用,提高存儲效率。

以 LevelDB 為例,LevelDB 是一個高效的鍵值對存儲引擎,它使用 Protobuf 來存儲數(shù)據(jù)的元信息和日志文件。通過將數(shù)據(jù)結(jié)構(gòu)序列化為 Protobuf 格式,LevelDB 可以更加緊湊地存儲數(shù)據(jù),減少磁盤 I/O 操作,提高數(shù)據(jù)讀寫的性能。在處理大量的鍵值對數(shù)據(jù)時,Protobuf 的高效性使得 LevelDB 能夠快速地定位和讀取數(shù)據(jù),滿足了許多對性能要求苛刻的應(yīng)用場景 。

在文件系統(tǒng)中,Protobuf 也有廣泛的應(yīng)用。比如在一些游戲開發(fā)中,游戲的配置文件、角色數(shù)據(jù)等可能會使用 Protobuf 進(jìn)行存儲。將這些數(shù)據(jù)以 Protobuf 格式保存到文件中,不僅可以節(jié)省存儲空間,而且在讀取和解析數(shù)據(jù)時速度更快,能夠讓游戲在加載和運(yùn)行過程中更加流暢 。

4.3網(wǎng)絡(luò)通信

在網(wǎng)絡(luò)通信方面,Protobuf 的優(yōu)勢尤為明顯。在客戶端 - 服務(wù)器通信中,使用 Protobuf 可以大大減少網(wǎng)絡(luò)帶寬的占用,提高通信效率。當(dāng)客戶端向服務(wù)器發(fā)送請求時,將請求數(shù)據(jù)序列化為 Protobuf 格式,由于其數(shù)據(jù)體積小,能夠更快地在網(wǎng)絡(luò)中傳輸;服務(wù)器接收到數(shù)據(jù)后,通過反序列化快速解析數(shù)據(jù),處理請求并返回響應(yīng)。這種高效的通信方式,特別適用于移動應(yīng)用與服務(wù)器之間的通信,因?yàn)橐苿釉O(shè)備的網(wǎng)絡(luò)帶寬和計(jì)算資源相對有限,Protobuf 能夠幫助減少數(shù)據(jù)傳輸量,降低網(wǎng)絡(luò)流量費(fèi)用,同時提高應(yīng)用的響應(yīng)速度 。

在微服務(wù)架構(gòu)中,各個微服務(wù)之間需要頻繁地進(jìn)行通信和數(shù)據(jù)交互。Protobuf 的跨語言支持和高效性,使得它成為微服務(wù)間通信的常用選擇。不同語言編寫的微服務(wù)可以通過 Protobuf 定義統(tǒng)一的數(shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)無縫的數(shù)據(jù)交換。例如,一個由 Java、Python 和 Go 語言編寫的微服務(wù)系統(tǒng),使用 Protobuf 來定義服務(wù)間的接口和數(shù)據(jù)格式,各個微服務(wù)可以根據(jù)生成的代碼進(jìn)行數(shù)據(jù)的序列化和反序列化,從而實(shí)現(xiàn)高效的通信和協(xié)作 。

此外,Protobuf 還可以作為 RESTful API 的替代方案,在一些對性能要求極高的場景中發(fā)揮作用。與 RESTful API 通常使用的 JSON 格式相比,Protobuf 的數(shù)據(jù)體積更小,解析速度更快。在實(shí)時數(shù)據(jù)傳輸、物聯(lián)網(wǎng)設(shè)備通信等場景中,使用 Protobuf 可以減少數(shù)據(jù)傳輸?shù)难舆t,提高系統(tǒng)的實(shí)時性和響應(yīng)能力 。

Part5.Protobuf與其他序列化協(xié)議的對比

5.1 Protobuf與 JSON 對比

JSON 作為一種輕量級的數(shù)據(jù)交換格式,在 Web 開發(fā)和數(shù)據(jù)傳輸中被廣泛應(yīng)用 。它以文本形式表示數(shù)據(jù),具有良好的可讀性和通用性。例如,一個簡單的用戶信息用 JSON 表示如下:

{
  "name": "張三",
  "age": 30,
  "email": "zhangsan@example.com"
}

從數(shù)據(jù)格式上看,JSON 是基于文本的鍵值對結(jié)構(gòu),易于人類閱讀和編寫,這使得它在需要快速查看和調(diào)試數(shù)據(jù)的場景中非常方便 。比如在前端開發(fā)中,JSON 數(shù)據(jù)可以直接被 JavaScript 解析和處理,與前端框架的結(jié)合非常緊密。

然而,與 Protobuf 相比,JSON 也存在一些明顯的缺點(diǎn)。在性能方面,由于 JSON 是文本格式,在序列化和反序列化時需要進(jìn)行大量的字符串解析和處理操作,導(dǎo)致其解析速度較慢。在處理大規(guī)模數(shù)據(jù)時,JSON 的解析時間可能會顯著增加,影響系統(tǒng)的響應(yīng)速度 。在空間占用上,JSON 的數(shù)據(jù)體積通常較大,因?yàn)樗舜罅康奈谋咀址?,如引號、逗號、冒號等,這些字符在傳輸和存儲時都會占用額外的空間。

在類型系統(tǒng)上,JSON是一種弱類型的數(shù)據(jù)格式,它對數(shù)據(jù)類型的檢查相對寬松 。在JSON數(shù)據(jù)中,數(shù)字類型可以被解析為整數(shù)或浮點(diǎn)數(shù),這在一些對數(shù)據(jù)類型要求嚴(yán)格的場景中可能會導(dǎo)致潛在的錯誤。而Protobuf是強(qiáng)類型的,在定義數(shù)據(jù)結(jié)構(gòu)時就明確了每個字段的類型,通過編譯器進(jìn)行類型檢查,能夠在編譯階段發(fā)現(xiàn)類型錯誤,提高了代碼的可靠性和穩(wěn)定性 。

在適用場景上,JSON更適合用于 Web API 的數(shù)據(jù)交互、配置文件的存儲以及對可讀性要求較高的場景 。在前后端數(shù)據(jù)交互中,JSON的通用性使得不同語言和平臺的應(yīng)用程序都能輕松處理。而 Protobuf 則更適合在對性能、空間占用要求較高,數(shù)據(jù)結(jié)構(gòu)相對穩(wěn)定的場景中使用,如分布式系統(tǒng)內(nèi)部通信、數(shù)據(jù)存儲等。

5.2 Protobuf與 XML 對比

XML(可擴(kuò)展標(biāo)記語言)也是一種常用的數(shù)據(jù)表示和交換格式,它具有自描述性和結(jié)構(gòu)化的特點(diǎn) 。以用戶信息為例,用 XML 表示如下:

<user>
  <name>張三</name>
  <age>30</age>
  <email>zhangsan@example.com</email>
</user>

從數(shù)據(jù)體積上看,XML 由于使用了大量的標(biāo)簽來描述數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)冗余度較高,導(dǎo)致其數(shù)據(jù)體積通常比 Protobuf 大很多 。在傳輸和存儲相同數(shù)據(jù)時,XML 需要占用更多的帶寬和存儲空間。例如,在一個包含復(fù)雜嵌套結(jié)構(gòu)的文檔中,XML 的標(biāo)簽嵌套會使得數(shù)據(jù)量大幅增加。

在解析速度方面,XML 的解析過程相對復(fù)雜,需要進(jìn)行標(biāo)簽匹配、語法檢查等操作,這使得 XML 的解析速度比 Protobuf 慢 。在處理大型 XML 文件時,解析時間可能會很長,影響系統(tǒng)的性能。

在語法復(fù)雜度上,XML 的語法規(guī)則較為嚴(yán)格,需要遵循正確的標(biāo)簽嵌套、屬性定義等規(guī)范,編寫和維護(hù) XML 文檔的成本相對較高 。而 Protobuf 的語法相對簡潔,通過.proto文件定義數(shù)據(jù)結(jié)構(gòu),更加直觀和易于理解。

在可擴(kuò)展性方面,雖然 XML 和 Protobuf 都支持?jǐn)?shù)據(jù)結(jié)構(gòu)的擴(kuò)展,但 Protobuf 在這方面表現(xiàn)得更加靈活和高效 。Protobuf 通過字段編號來標(biāo)識字段,在添加或刪除字段時,只要保持字段編號的唯一性,就可以實(shí)現(xiàn)向前和向后兼容,而 XML 在進(jìn)行結(jié)構(gòu)變更時,可能會對現(xiàn)有解析邏輯產(chǎn)生較大影響。

XML 更適用于對數(shù)據(jù)自描述性要求高、需要遵循特定標(biāo)準(zhǔn)規(guī)范(如 SOAP 協(xié)議)的場景 ,例如在企業(yè)級應(yīng)用中的數(shù)據(jù)交換和文檔存儲。而Protobuf則憑借其高效性和靈活性,在對性能和擴(kuò)展性要求較高的場景中更具優(yōu)勢 。

Part6.C++使用protobuf實(shí)現(xiàn)序列化的示例

在protobuf源碼中的 /examples 目錄下有官方提供的protobuf使用示例:addressbook.proto;參考官方示例實(shí)現(xiàn)C++使用protobuf進(jìn)行序列化和反序列化addressbook.proto :

syntax = "proto3";
package tutorial;

option optimize_for = LITE_RUNTIME;

message Person {
	string name = 1;
	int32 id = 2;
	string email = 3;

	enum PhoneType {
		MOBILE = 0;
		HOME = 1;
		WORK = 2;
	}

	message PhoneNumber {
		string number = 1;
		PhoneType type = 2;
	}

	repeated PhoneNumber phones = 4;
}

生成的addressbook.pb.h 文件內(nèi)容摘要:

namespace tutorial {
	class Person;
	class Person_PhoneNumber;
};

class Person_PhoneNumber : public MessageLite {
public:
	Person_PhoneNumber();
	virtual ~Person_PhoneNumber();
public:
	//string number = 1;
	void clear_number();
	const string& number() const;
	void set_number(const string& value);

	//int32 id = 2;
	void clear_id();
	int32 id() const;
	void set_id(int32 value);

	//string email = 3; 
	//...
};

add_person.cpp :

#include <iostream>
#include <fstream>
#include <string>
#include "pbs/addressbook.pb.h"
using namespace std;

void serialize_process() {
	cout << "serialize_process" << endl;
	tutorial::Person person;
	person.set_name("Obama");
	person.set_id(1234);
	person.set_email("1234@qq.com");

	tutorial::Person::PhoneNumber *phone1 = person.add_phones();
	phone1->set_number("110");
	phone1->set_type(tutorial::Person::MOBILE);

	tutorial::Person::PhoneNumber *phone2 = person.add_phones();
	phone2->set_number("119");
	phone2->set_type(tutorial::Person::HOME);

	fstream output("person_file", ios::out | ios::trunc | ios::binary);

	if( !person.SerializeToOstream(&output) ) {
		cout << "Fail to SerializeToOstream." << endl;
	}

	cout << "person.ByteSizeLong() : " << person.ByteSizLong() << endl;
}

void parse_process() {
	cout << "parse_process" << endl;
	tutorial::Person result;
	fstream input("person_file", ios::in | ios::binary);

	if(!result.ParseFromIstream(&input)) {
		cout << "Fail to ParseFromIstream." << endl;
	}

	cout << result.name() << endl;
	cout << result.id() << endl;
	cout << result.email() << endl;
	for(int i = 0; i < result.phones_size(); ++i) {
		const tutorial::Person::PhoneNumber &person_phone = result.phones(i);

		switch(person_phone.type()) {
			case tutorial::Person::MOBILE :
				cout << "MOBILE phone : ";
				break;
			case tutorial::Person::HOME :
				cout << "HOME phone : ";
				break;
			case tutorial::Person::WORK :
				cout << "WORK phone : ";
				break;
			default:
				cout << "phone type err." << endl;
		}
		cout << person_phone.number() << endl;
	}
}

int main(int argc, char *argv[]) {
	serialize_process();
	parse_process();

	google::protobuf::ShutdownProtobufLibrary();	//刪除所有已分配的內(nèi)存(Protobuf使用的堆內(nèi)存)
	return 0;
}

輸出結(jié)果:

[serialize_process]
person.ByteSizeLong() : 39
[parse_process]
Obama
1234
1234@qq.com
MOBILE phone : 110
HOME phone : 119

protobuf提供的序列化和反序列化的API接口函數(shù):

class MessageLite {
public:
	//序列化:
	bool SerializeToOstream(ostream* output) const;
	bool SerializeToArray(void *data, int size) const;
	bool SerializeToString(string* output) const;

	//反序列化:
	bool ParseFromIstream(istream* input);
	bool ParseFromArray(const void* data, int size);
	bool ParseFromString(const string& data);
};

三種序列化的方法沒有本質(zhì)上的區(qū)別,只是序列化后輸出的格式不同,可以供不同的應(yīng)用場景使用,序列化的API函數(shù)均為const成員函數(shù),因?yàn)樾蛄谢粫淖冾悓ο蟮膬?nèi)容, 而是將序列化的結(jié)果保存到函數(shù)入?yún)⒅付ǖ牡刂分小?/span>

.proto文件中的 option 選項(xiàng):

.proto文件中的option選項(xiàng)用于配置protobuf編譯后生成目標(biāo)語言文件中的代碼量,可設(shè)置為 SPEED, CODE_SIZE, LITE_RUNTIME 三種。

默認(rèn)option選項(xiàng)為 SPEED,常用的選項(xiàng)為 LITE_RUNTIME。

三者的區(qū)別在于:

① SPEED(默認(rèn)值):
表示生成的代碼運(yùn)行效率高,但是由此生成的代碼編譯后會占用更多的空間。

② CODE_SIZE:
與SPEED恰恰相反,代碼運(yùn)行效率較低,但是由此生成的代碼編譯后會占用更少的空間,
通常用于資源有限的平臺,如Mobile。

③ LITE_RUNTIME:
生成的代碼執(zhí)行效率高,同時生成代碼編譯后的所占用的空間也非常少。
這是以犧牲Protobuf提供的反射功能為代價的。
因此我們在C++中鏈接Protobuf庫時僅需鏈接libprotobuf-lite,而非protobuf。

SPEED 和 LITE_RUNTIME相比,在于調(diào)試級別上,例如 msg.SerializeToString(&str); 在 SPEED 模式下會利用反射機(jī)制打印出詳細(xì)字段和字段值,但是 LITE_RUNTIME 則僅僅打印字段值組成的字符串。

因此:可以在調(diào)試階段使用 SPEED 模式,而上線以后提升性能使用 LITE_RUNTIME 模式優(yōu)化。

最直觀的區(qū)別是使用三種不同的 option 選項(xiàng)時,編譯后產(chǎn)生的 .pb.h 中自定義的類所繼承的 protobuf類不同:

//1. SPEED模式:(自定義的類繼承自 Message 類)
// .proto 文件:
option optimize_for = SPEED;
// .pb.h 文件:
class Person : public ::PROTOBUF_NAMESPACE_ID::Message {};

//2. CODE_SIZE模式:(自定義的類繼承自 Message 類)
// .proto 文件:
option optimize_for = CODE_SIZE;
// .pb.h 文件:
class Person : public ::PROTOBUF_NAMESPACE_ID::Message {};

//3. LITE_RUNTIME模式:(自定義的類繼承自 MessageLite 類)
// .proto 文件:
option optimize_for = LITE_RUNTIME;
// .pb.h 文件:
class Person : public ::PROTOBUF_NAMESPACE_ID::MessageLite {};


責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2024-09-25 16:10:05

2015-05-08 12:41:36

C++序列化反序列化庫Kapok

2009-08-24 17:14:08

C#序列化

2009-08-06 11:16:25

C#序列化和反序列化

2009-09-09 16:53:49

C# XmlSeria序列化

2009-09-09 16:30:59

C# BinaryFo

2013-02-27 09:58:32

JavaJID

2013-02-28 10:00:43

JIDJava序列化

2011-06-01 14:50:48

2009-08-25 14:24:36

C#序列化和反序列化

2024-01-30 13:32:51

JSON反序列化序列化

2009-08-25 14:43:26

C#序列化和反序列化

2022-08-06 08:41:18

序列化反序列化Hessian

2025-06-18 09:29:24

2024-05-06 00:00:00

C#序列化技術(shù)

2011-06-01 15:05:02

序列化反序列化

2021-01-20 08:24:38

序列化內(nèi)存對象

2009-08-28 10:18:48

Java序列化

2009-08-25 14:59:39

C# XML序列化應(yīng)用

2009-09-09 15:54:48

C# XML序列化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號