外國優(yōu)秀網站設計青島關鍵詞優(yōu)化平臺
Protobuf 3 入門
1. 什么是序列化?
1.1 概念
序列化(Serialization 或 Marshalling) 是指將數(shù)據(jù)結構或對象的狀態(tài)轉換成可存儲或傳輸?shù)母袷?/strong>。反向操作稱為反序列化(Deserialization 或 Unmarshalling),它的作用是將序列化的數(shù)據(jù)恢復成原始的數(shù)據(jù)結構或對象。
簡單來說,序列化就像“打包”,反序列化就像“解包”。
2.1 為什么需要序列化?
在計算機系統(tǒng)中,數(shù)據(jù)通常是以內存中的對象(如 struct
、class
)形式存在的,而內存數(shù)據(jù)不能直接在不同程序之間傳輸,必須先轉換成可存儲或可傳輸?shù)母袷?。序列化的主要用途包?#xff1a;
- 數(shù)據(jù)存儲:將數(shù)據(jù)保存到文件、數(shù)據(jù)庫等,例如:
- 日志文件
- 配置文件(如 JSON、YAML)
- 持久化存儲(如 Redis、MongoDB)
- 數(shù)據(jù)傳輸:在不同進程或網絡之間傳輸數(shù)據(jù),例如:
- 前端和后端通信(Web API)
- 微服務之間的通信
- 遠程調用(RPC,如 gRPC)
- 數(shù)據(jù)緩存:比如將復雜的對象序列化后存入 Redis,提高訪問速度。
- 跨語言兼容:不同編程語言的數(shù)據(jù)結構不一樣,序列化后可以在不同語言之間傳輸數(shù)據(jù)。
3.1 序列化的方式
不同的序列化格式適用于不同的應用場景,常見的格式包括:
格式 | 特點 | 可讀性 | 序列化速度 | 數(shù)據(jù)大小 | 適用場景 |
---|---|---|---|---|---|
JSON | 文本格式,廣泛使用 | 可讀 | 適中 | 較大 | Web API,前端后端通信 |
XML | 結構化文本,標簽冗余 | 可讀 | 慢 | 大 | 早期 Web API,配置文件 |
YAML | 結構更簡潔,適合人閱讀 | 可讀 | 適中 | 較大 | 配置文件(Kubernetes、Docker) |
Protobuf | Google 開發(fā)的高效二進制格式 | 不可讀 | 快 | 小 | 微服務、gRPC、高性能應用 |
MessagePack | 類似 JSON,但體積更小 | 不可讀 | 快 | 小 | 移動端、嵌入式系統(tǒng) |
Thrift | Facebook 開發(fā)的高效序列化格式 | 不可讀 | 快 | 小 | 分布式系統(tǒng),RPC |
Avro | 適用于大數(shù)據(jù)(如 Hadoop) | 不可讀 | 適中 | 小 | 大數(shù)據(jù)處理 |
BSON | MongoDB 的序列化格式 | 不可讀 | 適中 | 適中 | MongoDB 數(shù)據(jù)存儲 |
2. 什么是 Protobuf?
2.1 概念
Protobuf(Protocol Buffers)是 Google 開發(fā)的一種高效、跨平臺、可擴展的數(shù)據(jù)序列化協(xié)議。它可以將數(shù)據(jù)轉換為緊湊的二進制格式,用于不同系統(tǒng)之間進行高效的數(shù)據(jù)傳輸和存儲。
簡單理解:
- 它類似于 JSON,但比 JSON 體積更小、速度更快。
- 它類似于 XML,但格式更緊湊、解析更高效。
- 它適用于微服務、RPC(遠程調用)、數(shù)據(jù)存儲等高性能場景。
2.2 為什么使用 Protobuf?
特點 | Protobuf | JSON | XML |
---|---|---|---|
格式 | 二進制 | 文本 | 文本 |
體積 | 最小 | 較大 | 最大 |
解析速度 | 最快 | 一般 | 最慢 |
可讀性 | 不可讀 | 可讀 | 可讀 |
跨語言支持 | 是 | 是 | 是 |
支持 RPC | 是(gRPC) | 否 | 否 |
如果你的項目涉及:
- 高性能數(shù)據(jù)通信(微服務、RPC、物聯(lián)網、游戲服務器)
- 跨語言數(shù)據(jù)傳輸(Go、Java、Python、C++、Rust 等)
- 大規(guī)模數(shù)據(jù)存儲(日志、數(shù)據(jù)庫、緩存)
那么 Protobuf 是比 JSON、XML 更好的選擇。
2.3 Protobuf 的使用場景
- 微服務通信(gRPC)
- 適用于 Go、Java、Python、C++ 等語言的微服務之間高效通信。
- 結合
gRPC
使用,可以比傳統(tǒng)REST API
更快。
- 數(shù)據(jù)存儲
- 存儲日志、緩存數(shù)據(jù)(如存入 Redis)時,Protobuf 體積小,能節(jié)省存儲空間。
- 跨語言數(shù)據(jù)交換
- 由于 Protobuf 支持多種編程語言,可以在不同語言的系統(tǒng)之間進行高效數(shù)據(jù)傳輸。
- 移動端和 IoT(物聯(lián)網)
- 移動端和 IoT 設備通常帶寬和存儲受限,Protobuf 適用于傳輸小體積數(shù)據(jù),提高性能。
3. 簡單解釋 Protobuf 例子
3.1 Protobuf 文件 simple.proto
syntax = "proto3"; // 使用 proto3 語法message SearchRequest { // 定義一個數(shù)據(jù)結構(類似 JSON 對象)string query = 1; // 搜索關鍵詞(字符串)int32 page_number = 2; // 頁碼(整數(shù))int32 result_per_page = 3; // 每頁返回的結果數(shù)(整數(shù))
}
解釋
syntax = "proto3";
指定使用proto3
語法。message SearchRequest
定義了一個數(shù)據(jù)結構(類似 JSON 對象)。- 每個字段的格式:
- 類型(
string
、int32
) - 字段名稱(
query
、page_number
、result_per_page
) - 字段編號(
1
、2
、3
,用于唯一標識字段,不能重復)
- 類型(
3.2 編譯 Protobuf 代碼
Protobuf 需要編譯后才能用于編程語言(Go、Java、Python 等)。 在終端運行:
protoc --go_out=. simple.proto
protoc
是 Protobuf 編譯器--go_out=.
表示生成 Go 代碼,并存放在當前目錄simple.proto
是需要編譯的 Protobuf 文件
不同語言對應的參數(shù):
語言 | 編譯參數(shù) |
---|---|
C++ | --cpp_out=. |
Java | --java_out=. |
Python | --python_out=. |
C# | --csharp_out=. |
Rust | --rust_out=. |
最終會生成 simple.pb.go
,這個文件包含 Go 代碼,用于操作 SearchRequest
結構。
3.3 生成的 Go 代碼
編譯后會生成如下 Go 結構:
type SearchRequest struct {Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`PageNumber int32 `protobuf:"varint,2,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"`ResultPerPage int32 `protobuf:"varint,3,opt,name=result_per_page,json=resultPerPage,proto3" json:"result_per_page,omitempty"`
}
解釋
SearchRequest
是struct
,對應.proto
文件中的message SearchRequest
。Query
、PageNumber
、ResultPerPage
變量對應.proto
里的字段。protobuf:"..."
里的信息用于 Protobuf 序列化和解析。
3.4 如何使用這個 Go 結構
package mainimport ("fmt""google.golang.org/protobuf/proto"
)func main() {// 創(chuàng)建 SearchRequest 實例request := &SearchRequest{Query: "golang protobuf",PageNumber: 1,ResultPerPage: 10,}// **序列化**data, _ := proto.Marshal(request)// **反序列化**newRequest := &SearchRequest{}proto.Unmarshal(data, newRequest)fmt.Println(newRequest) // 輸出: {Query:golang protobuf PageNumber:1 ResultPerPage:10}
}
解釋
- 創(chuàng)建
SearchRequest
結構,并填充數(shù)據(jù)。 - 使用
proto.Marshal(request)
序列化,轉換成二進制格式(適合網絡傳輸)。 - 使用
proto.Unmarshal(data, newRequest)
反序列化,把二進制恢復成 Go 結構。
4. Protobuf 的數(shù)據(jù)類型
4.1 標量數(shù)據(jù)類型(Scalar Types)
Protobuf 提供了一些常見的基本數(shù)據(jù)類型,對應不同語言的變量類型。
4.1.1 數(shù)值類型
Protobuf 類型 | 說明 | 適用場景 |
---|---|---|
int32 | 32 位整數(shù)(默認編碼) | 適用于較小的整數(shù) |
int64 | 64 位整數(shù)(默認編碼) | 適用于較大的整數(shù) |
uint32 | 無符號 32 位整數(shù) | 適用于只能為正數(shù)的情況 |
uint64 | 無符號 64 位整數(shù) | 適用于大數(shù)且不允許負數(shù) |
sint32 | 32 位有符號整數(shù)(ZigZag 編碼) | 適用于可能包含負數(shù)的整數(shù) |
sint64 | 64 位有符號整數(shù)(ZigZag 編碼) | 適用于包含負數(shù)的長整數(shù) |
fixed32 | 32 位整數(shù)(固定長度編碼) | 適用于數(shù)值分布較均勻的場景 |
fixed64 | 64 位整數(shù)(固定長度編碼) | 適用于較大的定長整數(shù) |
sfixed32 | 32 位有符號整數(shù)(固定長度編碼) | 適用于負數(shù)較多的場景 |
sfixed64 | 64 位有符號整數(shù)(固定長度編碼) | 適用于較大的負數(shù) |
區(qū)別:
int32/int64
:默認使用 Varint 編碼(數(shù)據(jù)小的時候占用字節(jié)更少)。sint32/sint64
:使用 ZigZag 編碼,負數(shù)編碼更高效。fixed32/fixed64
:使用固定長度存儲,適合數(shù)值分布均勻的情況。sfixed32/sfixed64
:固定長度的有符號整數(shù)。
4.1.2 浮點數(shù)類型
Protobuf 類型 | 說明 | 適用場景 |
---|---|---|
float | 32 位浮點數(shù) | 適用于存儲小數(shù) |
double | 64 位浮點數(shù) | 適用于更高精度的小數(shù) |
注意:
float
占 4 個字節(jié),精度有限。double
占 8 個字節(jié),適用于更高精度計算。
4.1.3 布爾類型
Protobuf 類型 | 說明 | 適用場景 |
---|---|---|
bool | 布爾值 (true/false ) | 適用于開關、狀態(tài)等 |
示例:
message Example {bool is_active = 1; // true or false
}
4.1.4 字符串和字節(jié)類型
Protobuf 類型 | 說明 | 適用場景 |
---|---|---|
string | UTF-8 編碼的字符串 | 存儲文本信息 |
bytes | 原始字節(jié)數(shù)據(jù) | 適用于存儲二進制數(shù)據(jù)(如文件、圖片等) |
示例:
message Example {string name = 1;bytes file_data = 2;
}
注意:
string
只能存儲 文本(UTF-8 編碼)。bytes
可以存儲 任意二進制數(shù)據(jù)(如圖片、視頻等)。
4.2 復雜數(shù)據(jù)類型
4.2.1 數(shù)組(Repeated)
使用 repeated
關鍵字表示 列表/數(shù)組:
message Example {repeated string hobbies = 1;repeated int32 scores = 2;
}
repeated string hobbies = 1;
→ 表示字符串數(shù)組repeated int32 scores = 2;
→ 表示整數(shù)數(shù)組
注意:
- 在 Protobuf 3 中,
repeated
類型默認是可選的,不需要額外的optional
關鍵字。
4.2.2 鍵值對(Map)
Protobuf 3 提供 map<K, V>
類型來存儲鍵值對:
message Example {map<string, int32> scores = 1; // key: string, value: int32
}
map<string, int32>
表示 鍵為字符串,值為整數(shù) 的字典。- 生成代碼后,會轉換成 Go 語言的
map[string]int32
。
4.2.3 枚舉類型(Enum)
enum Status {UNKNOWN = 0; // 枚舉必須從 0 開始ACTIVE = 1;INACTIVE = 2;
}message User {Status status = 1;
}
enum
只能用于定義固定的值(類似int
)。- 第一個枚舉值必須是 0,防止解析錯誤。
4.2.4 嵌套 Message
message Address {string city = 1;string street = 2;
}message Person {string name = 1;Address address = 2; // 直接嵌套 Address
}
Person
結構里包含Address
結構,可以用于復雜數(shù)據(jù)存儲。
4.3 Protobuf 類型與不同語言的對應關系
Protobuf 類型 | Go | Java | Python | C++ |
---|---|---|---|---|
int32 | int32 | int | int | int32_t |
int64 | int64 | long | int | int64_t |
float | float32 | float | float | float |
double | float64 | double | float | double |
bool | bool | boolean | bool | bool |
string | string | String | str | std::string |
bytes | []byte | byte[] | bytes | std::string |
map<K,V> | map[K]V | Map<K,V> | dict | std::map<K,V> |
repeated | []T | List<T> | list | std::vector<T> |
4.4 Protobuf 3 語法示例
syntax = "proto3";message Person {string name = 1;int32 age = 2;bool is_active = 3;repeated string hobbies = 4;map<string, int32> scores = 5;
}
這個 Person
結構包含:
string name
→ 姓名int32 age
→ 年齡bool is_active
→ 是否激活repeated string hobbies
→ 興趣愛好(數(shù)組)map<string, int32> scores
→ 課程成績(鍵值對)
5. Protobuf 其他字段
5.1 Oneof(互斥字段)
5.1.1 什么是 oneof
?
oneof
關鍵字用于定義一組互斥字段,即同一時間只能有一個字段被設置。它的作用類似于 C 語言的 union
,但比 union
更智能,可以判斷當前設置的是哪個字段。
5.1.2 為什么要用 oneof
?
在 proto3
版本中,所有字段都有默認值,比如:
message Example {int64 id = 1;
}
- 如果
id
沒有被設置,默認值是0
。 - 但如果
id
被顯式設置為0
,你就無法判斷這個0
是默認值,還是用戶真的設置了0
。
oneof
解決了這個問題,因為它提供了一個字段狀態(tài)檢查功能,讓你可以判斷哪個字段被設置了。
5.1.3 oneof
語法
message Response {oneof result {string success_message = 1; // 成功時的消息int32 error_code = 2; // 失敗時的錯誤碼}
}
oneof
內的字段是互斥的,最多只能設置一個。- 如果
success_message
被設置,error_code
就不能被設置,反之亦然。 - 如果不設置任何字段,
oneof
字段為空。
適用場景
- API 響應(成功返回
success_message
,失敗返回error_code
)。- 狀態(tài)表示(例如訂單可能是“待支付”或“已完成”,但不能同時處于這兩個狀態(tài))。
5.1.4 oneof
在 Go 語言中的使用
Protobuf 生成的 Go
代碼會使用 isXxx()
方法 來判斷哪個字段被賦值。
示例:Go 代碼
package mainimport ("fmt""google.golang.org/protobuf/proto"
)// 假設 Protobuf 生成的 Go 結構如下:
type Response struct {// 這是 oneof 生成的字段Result isResponse_Result `protobuf_oneof:"result"`
}type isResponse_Result interface {isResponse_Result()
}type Response_SuccessMessage struct {SuccessMessage string
}type Response_ErrorCode struct {ErrorCode int32
}// 實現(xiàn) isResponse_Result 接口
func (*Response_SuccessMessage) isResponse_Result() {}
func (*Response_ErrorCode) isResponse_Result() {}func main() {// **成功時返回 success_message**resp1 := &Response{Result: &Response_SuccessMessage{SuccessMessage: "Operation successful"}}// **失敗時返回 error_code**resp2 := &Response{Result: &Response_ErrorCode{ErrorCode: 404}}// 判斷是哪個字段被設置switch v := resp1.Result.(type) {case *Response_SuccessMessage:fmt.Println("Success:", v.SuccessMessage)case *Response_ErrorCode:fmt.Println("Error:", v.ErrorCode)}switch v := resp2.Result.(type) {case *Response_SuccessMessage:fmt.Println("Success:", v.SuccessMessage)case *Response_ErrorCode:fmt.Println("Error:", v.ErrorCode)}
}
輸出
Success: Operation successful
Error: 404
oneof
生成了isResponse_Result
接口,允許我們判斷哪個字段被設置。switch v := resp.Result.(type)
語法用于檢查當前oneof
字段的類型。
5.1.5 oneof
的應用場景
- REST API / gRPC 響應(成功返回數(shù)據(jù),失敗返回錯誤碼)
- 訂單狀態(tài)(
未支付
/已支付
) - 用戶身份驗證(
郵箱登錄
/手機號登錄
) - 存儲不同類型的數(shù)據(jù)(
文本
/圖片
/視頻
)
5.2 Reserved(保留字段)
5.2.1 什么是reserved
?
在 Protobuf 3 中,reserved
關鍵字用于保留字段編號或名稱,防止將來代碼演進時誤用已刪除的字段。
這可以避免 API 變更時的兼容性問題,確保舊數(shù)據(jù)不會被錯誤解析。
5.2.2 為什么需要 reserved
?
當你刪除或修改字段時,如果不使用 reserved
,那么:
- 未來新添加的字段可能意外復用舊的字段編號,導致數(shù)據(jù)解析出錯。
- 舊數(shù)據(jù)仍然可能使用被刪除的字段,導致意外行為。
5.2.3 reserved
語法
你可以用 reserved
關鍵字保留字段編號或名稱,防止后續(xù)被重新使用。
保留字段編號
message User {reserved 2, 4 to 6; // 不能再使用編號 2、4、5、6
}
- 以后不能再使用 2、4、5、6 作為字段編號。
- 如果后續(xù)嘗試用
field = 2;
,編譯時會報錯。
保留字段名稱
message User {reserved "old_name", "deprecated_field"; // 不能再使用這些字段名
}
- 以后不能再使用 “old_name” 和 “deprecated_field” 作為字段名。
同時保留編號和名稱
message User {reserved 2, 4 to 6;reserved "old_name", "deprecated_field";
}
- 這樣可以同時保留編號和字段名稱,防止意外復用。
5.2.4 reserved
作用
- 避免舊數(shù)據(jù)解析錯誤:如果編號被誤用,舊數(shù)據(jù)可能被錯誤解析。
- 防止 API 兼容性問題:如果 API 變更,保留字段可以確保舊客戶端不會收到無效數(shù)據(jù)。
- 讓代碼更可維護:明確告訴后續(xù)開發(fā)者哪些字段不能使用。
5.3 Any(存儲任意數(shù)據(jù))
5.3.1 什么是 Any
?
Any
是 Protobuf 3 提供的一種特殊類型,允許存儲任意類型的 Protobuf 消息,適用于動態(tài)數(shù)據(jù)場景。
它可以在不修改 .proto
結構的情況下,支持不同類型的數(shù)據(jù),類似于 JSON 里的 object
或 map<string, any>
。
5.3.2 Any
的作用
- 存儲動態(tài)數(shù)據(jù):如果一個字段的類型可能變化(例如可能是
User
或Order
),可以使用Any
而不需要改.proto
文件。 - 實現(xiàn)靈活的 API 設計:適用于插件系統(tǒng)、事件系統(tǒng)、日志系統(tǒng),讓不同的子系統(tǒng)傳遞不同的數(shù)據(jù)結構。
- 避免頻繁修改 Protobuf 定義:當不同的客戶端需要傳輸不同的數(shù)據(jù)類型時,使用
Any
可以減少 API 變更的影響。
5.3.3 Any
的基本用法
(1)導入 Any
類型
Any
需要導入 google/protobuf/any.proto
:
import "google/protobuf/any.proto"; // 引入 Any 類型message Response {string message = 1; google.protobuf.Any data = 2; // 存儲任意類型
}
message
是普通字段,存儲文本信息。data
是Any
類型,可以存儲任何 Protobuf 消息。
(2)嵌套不同的消息
假設你有兩種不同的消息 User
和 Order
:
message User {string name = 1;int32 age = 2;
}message Order {int32 order_id = 1;double price = 2;
}message Response {string message = 1;google.protobuf.Any data = 2; // 可以存儲 User 或 Order
}
data
字段可以存儲User
或Order
,而不需要修改Response
結構。- 這樣,
Response
可以在不同場景下使用,不受數(shù)據(jù)類型影響。
5.3.4 Any
在 Go 語言中的使用
(1)安裝 Protobuf 依賴
在 Go 代碼中,需要 proto
和 anypb
(處理 Any
類型):
go get google.golang.org/protobuf/proto
go get google.golang.org/protobuf/types/known/anypb
(2)Go 代碼示例
package mainimport ("fmt""google.golang.org/protobuf/proto""google.golang.org/protobuf/types/known/anypb"
)// 定義 User 和 Order 結構
type User struct {Name stringAge int32
}type Order struct {OrderId int32Price float64
}// Response 結構,包含 Any 字段
type Response struct {Message stringData *anypb.Any
}func main() {// 創(chuàng)建 User 結構user := &User{Name: "Alice", Age: 25}// 將 User 結構封裝到 Any 里anyData, _ := anypb.New(user)// 創(chuàng)建 Response 并存儲 User 數(shù)據(jù)resp := &Response{Message: "User data",Data: anyData,}// **序列化**data, _ := proto.Marshal(resp)// **反序列化**newResp := &Response{}proto.Unmarshal(data, newResp)// **解析 Any 字段**newUser := &User{}newResp.Data.UnmarshalTo(newUser)fmt.Println("Message:", newResp.Message)fmt.Println("User Name:", newUser.Name, "Age:", newUser.Age)
}
(3)Go 代碼解釋
- 封裝數(shù)據(jù):
- 使用
anypb.New(user)
把User
結構轉換成Any
類型。
- 使用
- 序列化
Response
:- 使用
proto.Marshal(resp)
進行序列化,便于存儲或傳輸。
- 使用
- 反序列化
Response
:- 使用
proto.Unmarshal(data, newResp)
解析Response
結構。
- 使用
- 解析
Any
數(shù)據(jù):newResp.Data.UnmarshalTo(newUser)
解析Any
字段,恢復User
結構。
6. Protobuf 編碼原理
Protobuf 使用高效的二進制格式來存儲和傳輸數(shù)據(jù),其中最關鍵的編碼方式之一是 Varint(變長整數(shù)編碼)。它的核心思想是:
- 數(shù)值越小,占用字節(jié)越少
- 數(shù)值越大,占用字節(jié)越多
- 高效存儲,減少帶寬消耗
6.1 什么是 Varint?
Varint(變長整數(shù)編碼) 是一種特殊的編碼方式,它可以使用 1 到 N 個字節(jié) 表示整數(shù)。
- 小數(shù)占用更少字節(jié)(如
1
只需要 1 個字節(jié))。 - 大數(shù)會自動擴展到多個字節(jié)(如
300
需要 2 個字節(jié))。
6.2 Varint 編碼規(guī)則
- 每個字節(jié)的最高位(MSB,Most Significant Bit)是“是否還有后續(xù)字節(jié)的標志”
- 最高位為
0
:表示這是最后一個字節(jié)。 - 最高位為
1
:表示后面還有字節(jié)。
- 最高位為
- 剩下的 7 位存儲數(shù)據(jù)(低位優(yōu)先,LSB)。
6.3 具體示例
(1)數(shù)字 1
的 Varint 編碼
0000 0001 (只有 1 個字節(jié))
- 最高位
0
:表示這是最后一個字節(jié)。 - 其余 7 位
000 0001
(= 1)。
存儲方式:
[0000 0001] → 1 字節(jié)
(2)數(shù)字 300
的 Varint 編碼
先看二進制表示:
300 = 100101100(9 位)
需要拆成 7 位 + 剩余部分:
低 7 位: 0101100 → 0x2C(44)
高 2 位: 0000010 → 0x02(2)
- 第一字節(jié):
1010 1100
(0xAC)- 最高位
1
(表示后面還有字節(jié))。 - 剩余
7
位存010 1100
(= 44)。
- 最高位
- 第二字節(jié):
0000 0010
(0x02)- 最高位
0
(表示這是最后一個字節(jié))。 - 剩余
7
位存000 0010
(= 2)。
- 最高位
最終編碼
[1010 1100] [0000 0010] → 2 字節(jié)(0xAC 0x02)
6.4 Wire Type(數(shù)據(jù)類型編碼)
Protobuf 數(shù)據(jù)存儲為 鍵值對(key-value) 形式,每個字段的 key
也需要編碼。
字段的 key
由字段編號 + Wire Type 組成。
Wire Type | 值 | 作用 |
---|---|---|
Varint | 0 | 變長整數(shù)(int32, int64, bool, enum ) |
Fixed64 | 1 | 64 位定長(double, fixed64, sfixed64 ) |
Length-delimited | 2 | 變長數(shù)據(jù)(string, bytes, message, repeated ) |
Start group | 3 | 已廢棄(用于嵌套數(shù)據(jù)) |
End group | 4 | 已廢棄 |
Fixed32 | 5 | 32 位定長(float, fixed32, sfixed32 ) |
存儲格式
[字段編號 << 3] | [Wire Type] [數(shù)據(jù)]
字段編號左移 3 位,低 3 位存 Wire Type。
6.5 例子:Protobuf 編碼解析
假設 Person
結構如下:
message Person {int32 id = 1; // 1 字段編號string name = 2; // 2 字段編號
}
數(shù)據(jù):
{"id": 150,"name": "Alice"
}
編碼過程
-
字段
id = 150
- 字段編號 =
1
- Wire Type =
0
(Varint) key = (1 << 3) | 0 = 0000 1000 (0x08)
150
的 Varint 編碼:1001 0110 0000 0001
(0x96 0x01)
最終存儲:
[0x08] [0x96 0x01] (字段編號 1,Varint)
- 字段編號 =
-
字段
name = "Alice"
- 字段編號 =
2
- Wire Type =
2
(Length-delimited,字符串) key = (2 << 3) | 2 = 0001 0001 (0x12)
"Alice"
= 5 個字節(jié)(0x41 0x6C 0x69 0x63 0x65
)
最終存儲:
[0x12] [0x05] [0x41 0x6C 0x69 0x63 0x65]
- 字段編號 =
6.6 解析 Protobuf 二進制數(shù)據(jù)
假設收到如下二進制數(shù)據(jù):
08 96 01 12 05 41 6C 69 63 65
逐字節(jié)解析:
08
=0000 1000
(字段編號1
,Wire Type0
,Varint)96 01
= 150(Varint 解碼)12
=0001 0010
(字段編號2
,Wire Type2
,字符串)05
= 長度5
41 6C 69 63 65
="Alice"
最終解析為:
{"id": 150,"name": "Alice"
}