Linux中國

如何使用 Protobuf 做數據交換

協議緩衝區 Protocol Buffers Protobufs)像 XMLJSON 一樣,可以讓用不同語言編寫並在不同平台上運行的應用程序交換數據。例如,用 Go 編寫的發送程序可以在 Protobuf 中對以 Go 表示的銷售訂單數據進行編碼,然後用 Java 編寫的接收方可以對它進行解碼,以獲取所接收訂單數據的 Java 表示方式。這是在網路連接上的結構示意圖:

Go 銷售訂單 —> Pbuf 編碼 —> 網路 —> Pbuf 界面 —> Java 銷售訂單

XMLJSON 相比,Protobuf 編碼是二進位而不是文本,這會使調試複雜化。但是,正如本文中的代碼示例所確認的那樣,Protobuf 編碼在大小上比 XML 或 JSON 編碼要有效得多。

Protobuf 以另一種方式提供了這種有效性。在實現級別,Protobuf 和其他編碼系統對結構化數據進行 序列化 serialize 反序列化 deserialize 。序列化將特定語言的數據結構轉換為位元組流,反序列化是將位元組流轉換回特定語言的數據結構的逆運算。序列化和反序列化可能成為數據交換的瓶頸,因為這些操作會佔用大量 CPU。高效的序列化和反序列化是 Protobuf 的另一個設計目標。

最近的編碼技術,例如 Protobuf 和 FlatBuffers,源自 1990 年代初期的 DCE/RPC 分散式計算環境/遠程過程調用 Distributed Computing Environment/Remote Procedure Call )計劃。與 DCE/RPC 一樣,Protobuf 在數據交換中為 IDL(介面定義語言)和編碼層做出了貢獻。

本文將著眼於這兩層,然後提供 Go 和 Java 中的代碼示例以充實 Protobuf 的細節,並表明 Protobuf 是易於使用的。

Protobuf 作為一個 IDL 和編碼層

像 Protobuf 一樣,DCE/RPC 被設計為與語言和平台無關。適當的庫和實用程序允許任何語言和平台用於 DCE/RPC 領域。此外,DCE/RPC 體系結構非常優雅。IDL 文檔是一側的遠程過程與另一側的調用者之間的協定。Protobuf 也是以 IDL 文檔為中心的。

IDL 文檔是文本,在 DCE/RPC 中,使用基本 C 語法以及元數據的語法擴展(方括弧)和一些新的關鍵字,例如 interface。這是一個例子:

[uuid (2d6ead46-05e3-11ca-7dd1-426909beabcd), version(1.0)]
interface echo {
   const long int ECHO_SIZE = 512;
   void echo(
      [in]          handle_t h,
      [in, string]  idl_char from_client[ ],
      [out, string] idl_char from_service[ECHO_SIZE]
   );
}

該 IDL 文檔聲明了一個名為 echo 的過程,該過程帶有三個參數:類型為 handle_t(實現指針)和 idl_char(ASCII 字元數組)的 [in] 參數被傳遞給遠程過程,而 [out] 參數(也是一個字元串)從該過程中傳回。在此示例中,echo 過程不會顯式返回值(echo 左側的 void),但也可以返回值。返回值,以及一個或多個 [out] 參數,允許遠程過程任意返回許多值。下一節將介紹 Protobuf IDL,它的語法不同,但同樣用作數據交換中的協定。

DCE/RPC 和 Protobuf 中的 IDL 文檔是創建用於交換數據的基礎結構代碼的實用程序的輸入:

IDL 文檔 —> DCE/PRC 或 Protobuf 實用程序 —> 數據交換的支持代碼

作為相對簡單的文本,IDL 是同樣便於人類閱讀的關於數據交換細節的文檔(特別是交換的數據項的數量和每個項的數據類型)。

Protobuf 可用於現代 RPC 系統,例如 gRPC;但是 Protobuf 本身僅提供 IDL 層和編碼層,用於從發送者傳遞到接收者的消息。與原本的 DCE/RPC 一樣,Protobuf 編碼是二進位的,但效率更高。

目前,XML 和 JSON 編碼仍在通過 Web 服務等技術進行的數據交換中佔主導地位,這些技術利用 Web 伺服器、傳輸協議(例如 TCP、HTTP)以及標準庫和實用程序等原有的基礎設施來處理 XML 和 JSON 文檔。 此外,各種類型的資料庫系統可以存儲 XML 和 JSON 文檔,甚至舊式關係型系統也可以輕鬆生成查詢結果的 XML 編碼。現在,每種通用編程語言都具有支持 XML 和 JSON 的庫。那麼,是什麼讓我們回到 Protobuf 之類的二進位編碼系統呢?

讓我們看一下負十進位值 -128。以 2 的補碼二進位表示形式(在系統和語言中佔主導地位)中,此值可以存儲在單個 8 位位元組中:10000000。此整數值在 XML 或 JSON 中的文本編碼需要多個位元組。例如,UTF-8 編碼需要四個位元組的字元串,即 -128,即每個字元一個位元組(十六進位,值為 0x2d0x310x320x38)。XML 和 JSON 還添加了標記字元,例如尖括弧和大括弧。有關 Protobuf 編碼的詳細信息下面就會介紹,但現在的關注點是一個通用點:文本編碼的壓縮性明顯低於二進位編碼。

在 Go 中使用 Protobuf 的示例

我的代碼示例著重於 Protobuf 而不是 RPC。以下是第一個示例的概述:

  • 名為 dataitem.proto 的 IDL 文件定義了一個 Protobuf 消息,它具有六個不同類型的欄位:具有不同範圍的整數值、固定大小的浮點值以及兩個不同長度的字元串。
  • Protobuf 編譯器使用 IDL 文件生成 Go 版本(以及後面的 Java 版本)的 Protobuf 消息及支持函數。
  • Go 應用程序使用隨機生成的值填充原生的 Go 數據結構,然後將結果序列化為本地文件。為了進行比較, XML 和 JSON 編碼也被序列化為本地文件。
  • 作為測試,Go 應用程序通過反序列化 Protobuf 文件的內容來重建其原生數據結構的實例。
  • 作為語言中立性測試,Java 應用程序還會對 Protobuf 文件的內容進行反序列化以獲取原生數據結構的實例。

我的網站上提供了該 IDL 文件以及兩個 Go 和一個 Java 源文件,打包為 ZIP 文件。

最重要的 Protobuf IDL 文檔如下所示。該文檔存儲在文件 dataitem.proto 中,並具有常規的.proto 擴展名。

示例 1、Protobuf IDL 文檔

syntax = "proto3";

package main;

message DataItem {
  int64  oddA  = 1;
  int64  evenA = 2;
  int32  oddB  = 3;
  int32  evenB = 4;
  float  small = 5;
  float  big   = 6;
  string short = 7;
  string long  = 8;
}

該 IDL 使用當前的 proto3 而不是較早的 proto2 語法。軟體包名稱(在本例中為 main)是可選的,但是慣例使用它以避免名稱衝突。這個結構化的消息包含八個欄位,每個欄位都有一個 Protobuf 數據類型(例如,int64string)、名稱(例如,oddAshort)和一個等號 = 之後的數字標籤(即鍵)。標籤(在此示例中為 1 到 8)是唯一的整數標識符,用於確定欄位序列化的順序。

Protobuf 消息可以嵌套到任意級別,而一個消息可以是另外一個消息的欄位類型。這是一個使用 DataItem 消息作為欄位類型的示例:

message DataItems {
  repeated DataItem item = 1;
}

單個 DataItems 消息由重複的(零個或多個)DataItem 消息組成。

為了清晰起見,Protobuf 還支持枚舉類型:

enum PartnershipStatus {
  reserved "FREE", "CONSTRAINED", "OTHER";
}

reserved 限定符確保用於實現這三個符號名的數值不能重複使用。

為了生成一個或多個聲明 Protobuf 消息結構的特定於語言的版本,包含這些結構的 IDL 文件被傳遞到protoc 編譯器(可在 Protobuf GitHub 存儲庫中找到)。對於 Go 代碼,可以以通常的方式安裝支持的 Protobuf 庫(這裡以 作為命令行提示符):

% go get github.com/golang/protobuf/proto

將 Protobuf IDL 文件 dataitem.proto 編譯為 Go 源代碼的命令是:

% protoc --go_out=. dataitem.proto

標誌 --go_out 指示編譯器生成 Go 源代碼。其他語言也有類似的標誌。在這種情況下,結果是一個名為 dataitem.pb.go 的文件,該文件足夠小,可以將其基本內容複製到 Go 應用程序中。以下是生成的代碼的主要部分:

var _ = proto.Marshal

type DataItem struct {
   OddA  int64   `protobuf:"varint,1,opt,name=oddA" json:"oddA,omitempty"`
   EvenA int64   `protobuf:"varint,2,opt,name=evenA" json:"evenA,omitempty"`
   OddB  int32   `protobuf:"varint,3,opt,name=oddB" json:"oddB,omitempty"`
   EvenB int32   `protobuf:"varint,4,opt,name=evenB" json:"evenB,omitempty"`
   Small float32 `protobuf:"fixed32,5,opt,name=small" json:"small,omitempty"`
   Big   float32 `protobuf:"fixed32,6,opt,name=big" json:"big,omitempty"`
   Short string  `protobuf:"bytes,7,opt,name=short" json:"short,omitempty"`
   Long  string  `protobuf:"bytes,8,opt,name=long" json:"long,omitempty"`
}

func (m *DataItem) Reset()         { *m = DataItem{} }
func (m *DataItem) String() string { return proto.CompactTextString(m) }
func (*DataItem) ProtoMessage()    {}
func init() {}

編譯器生成的代碼具有 Go 結構 DataItem,該結構導出 Go 欄位(名稱現已大寫開頭),該欄位與 Protobuf IDL 中聲明的名稱匹配。該結構欄位具有標準的 Go 數據類型:int32int64float32string。在每個欄位行的末尾,是描述 Protobuf 類型的字元串,提供 Protobuf IDL 文檔中的數字標籤及有關 JSON 信息的元數據,這將在後面討論。

此外也有函數;最重要的是 Proto.Marshal,用於將 DataItem 結構的實例序列化為 Protobuf 格式。輔助函數包括:清除 DataItem 結構的 Reset,生成 DataItem 的單行字元串表示的 String

描述 Protobuf 編碼的元數據應在更詳細地分析 Go 程序之前進行仔細研究。

Protobuf 編碼

Protobuf 消息的結構為鍵/值對的集合,其中數字標籤為鍵,相應的欄位為值。欄位名稱(例如,oddAsmall)是供人類閱讀的,但是 protoc 編譯器的確使用了欄位名稱來生成特定於語言的對應名稱。例如,Protobuf IDL 中的 oddAsmall 名稱在 Go 結構中分別成為欄位 OddASmall

鍵和它們的值都被編碼,但是有一個重要的區別:一些數字值具有固定大小的 32 或 64 位的編碼,而其他數字(包括消息標籤)則是 varint 編碼的,位數取決於整數的絕對值。例如,整數值 1 到 15 需要 8 位 varint 編碼,而值 16 到 2047 需要 16 位。varint 編碼在本質上與 UTF-8 編碼類似(但細節不同),它偏愛較小的整數值而不是較大的整數值。(有關詳細分析,請參見 Protobuf 編碼指南)結果是,Protobuf 消息應該在欄位中具有較小的整數值(如果可能),並且鍵數應儘可能少,但每個欄位至少得有一個鍵。

下表 1 列出了 Protobuf 編碼的要點:

編碼 示例類型 長度
varint int32uint32int64 可變長度
fixed fixed32floatdouble 固定的 32 位或 64 位長度
位元組序列 stringbytes 序列長度

表 1. Protobuf 數據類型

未明確固定長度的整數類型是 varint 編碼的;因此,在 varint 類型中,例如 uint32u 代表無符號),數字 32 描述了整數的範圍(在這種情況下為 0 到 2 32 - 1),而不是其位的大小,該位大小取決於值。相比之下,對於固定長度類型(例如 fixed32double),Protobuf 編碼分別需要 32 位和 64 位。Protobuf 中的字元串是位元組序列;因此,欄位編碼的大小就是位元組序列的長度。

另一個高效的方法值得一提。回想一下前面的示例,其中的 DataItems 消息由重複的 DataItem 實例組成:

message DataItems {
  repeated DataItem item = 1;
}

repeated 表示 DataItem 實例是打包的:集合具有單個標籤,在這裡是 1。因此,具有重複的 DataItem 實例的 DataItems 消息比具有多個但單獨的 DataItem 欄位、每個欄位都需要自己的標籤的消息的效率更高。

了解了這一背景,讓我們回到 Go 程序。

dataItem 程序的細節

dataItem 程序創建一個 DataItem 實例,並使用適當類型的隨機生成的值填充欄位。Go 有一個 rand 包,帶有用於生成偽隨機整數和浮點值的函數,而我的 randString 函數可以從字符集中生成指定長度的偽隨機字元串。設計目標是要有一個具有不同類型和位大小的欄位值的 DataItem 實例。例如,OddAEvenA 值分別是 64 位非負整數值的奇數和偶數;但是 OddBEvenB 變體的大小為 32 位,並存放 0 到 2047 之間的小整數值。隨機浮點值的大小為 32 位,字元串為 16(Short)和 32(Long)字元的長度。這是用隨機值填充 DataItem 結構的代碼段:

// 可變長度整數
n1 := rand.Int63()        // 大整數
if (n1 & 1) == 0 { n1++ } // 確保其是奇數
...
n3 := rand.Int31() % UpperBound // 小整數
if (n3 & 1) == 0 { n3++ }       // 確保其是奇數

// 固定長度浮點數
...
t1 := rand.Float32()
t2 := rand.Float32()
...
// 字元串
str1 := randString(StrShort)
str2 := randString(StrLong)

// 消息
dataItem := &DataItem {
   OddA:  n1,
   EvenA: n2,
   OddB:  n3,
   EvenB: n4,
   Big:   f1,
   Small: f2,
   Short: str1,
   Long:  str2,
}

創建並填充值後,DataItem 實例將以 XML、JSON 和 Protobuf 進行編碼,每種編碼均寫入本地文件:

func encodeAndserialize(dataItem *DataItem) {
   bytes, _ := xml.MarshalIndent(dataItem, "", " ")  // Xml to dataitem.xml
   ioutil.WriteFile(XmlFile, bytes, 0644)            // 0644 is file access permissions

   bytes, _ = json.MarshalIndent(dataItem, "", " ")  // Json to dataitem.json
   ioutil.WriteFile(JsonFile, bytes, 0644)

   bytes, _ = proto.Marshal(dataItem)                // Protobuf to dataitem.pbuf
   ioutil.WriteFile(PbufFile, bytes, 0644)
}

這三個序列化函數使用術語 marshal,它與 serialize 意思大致相同。如代碼所示,三個 Marshal 函數均返回一個位元組數組,然後將其寫入文件。(為簡單起見,忽略可能的錯誤處理。)在示例運行中,文件大小為:

dataitem.xml:  262 bytes
dataitem.json: 212 bytes
dataitem.pbuf:  88 bytes

Protobuf 編碼明顯小於其他兩個編碼方案。通過消除縮進字元(在這種情況下為空白和換行符),可以稍微減小 XML 和 JSON 序列化的大小。

以下是 dataitem.json 文件,該文件最終是由 json.MarshalIndent 調用產生的,並添加了以 ## 開頭的注釋:

{
 "oddA":  4744002665212642479,                ## 64-bit >= 0
 "evenA": 2395006495604861128,                ## ditto
 "oddB":  57,                                 ## 32-bit >= 0 but < 2048
 "evenB": 468,                                ## ditto
 "small": 0.7562016,                          ## 32-bit floating-point
 "big":   0.85202795,                         ## ditto
 "short": "ClH1oDaTtoX$HBN5",                 ## 16 random chars
 "long":  "xId0rD3Cri%3Wt%^QjcFLJgyXBu9^DZI"  ## 32 random chars
}

儘管這些序列化的數據寫入到本地文件中,但是也可以使用相同的方法將數據寫入網路連接的輸出流。

測試序列化和反序列化

Go 程序接下來通過將先前寫入 dataitem.pbuf 文件的位元組反序列化為 DataItem 實例來運行基本測試。這是代碼段,其中去除了錯誤檢查部分:

filebytes, err := ioutil.ReadFile(PbufFile) // get the bytes from the file
...
testItem.Reset()                            // clear the DataItem structure
err = proto.Unmarshal(filebytes, testItem)  // deserialize into a DataItem instance

用於 Protbuf 反序列化的 proto.Unmarshal 函數與 proto.Marshal 函數相反。原始的 DataItem 和反序列化的副本將被列印出來以確認完全匹配:

Original:
2041519981506242154 3041486079683013705 1192 1879
0.572123 0.326855
boPb#T0O8Xd&Ps5EnSZqDg4Qztvo7IIs 9vH66AiGSQgCDxk&

Deserialized:
2041519981506242154 3041486079683013705 1192 1879
0.572123 0.326855
boPb#T0O8Xd&Ps5EnSZqDg4Qztvo7IIs 9vH66AiGSQgCDxk&

一個 Java Protobuf 客戶端

用 Java 寫的示例是為了確認 Protobuf 的語言中立性。原始 IDL 文件可用於生成 Java 支持代碼,其中涉及嵌套類。但是,為了抑制警告信息,可以進行一些補充。這是修訂版,它指定了一個 DataMsg 作為外部類的名稱,內部類在該 Protobuf 消息後面自動命名為 DataItem

syntax = "proto3";

package main;

option java_outer_classname = "DataMsg";

message DataItem {
...

進行此更改後,protoc 編譯與以前相同,只是所期望的輸出現在是 Java 而不是 Go:

% protoc --java_out=. dataitem.proto

生成的源文件(在名為 main 的子目錄中)為 DataMsg.java,長度約為 1,120 行:Java 並不簡潔。編譯然後運行 Java 代碼需要具有 Protobuf 庫支持的 JAR 文件。該文件位於 Maven 存儲庫中。

放置好這些片段後,我的測試代碼相對較短(並且在 ZIP 文件中以 Main.java 形式提供):

package main;
import java.io.FileInputStream;

public class Main {
   public static void main(String[] args) {
      String path = "dataitem.pbuf";  // from the Go program&apos;s serialization
      try {
         DataMsg.DataItem deserial =
           DataMsg.DataItem.newBuilder().mergeFrom(new FileInputStream(path)).build();

         System.out.println(deserial.getOddA()); // 64-bit odd
         System.out.println(deserial.getLong()); // 32-character string
      }
      catch(Exception e) { System.err.println(e); }
    }
}

當然,生產級的測試將更加徹底,但是即使是該初步測試也可以證明 Protobuf 的語言中立性:dataitem.pbuf 文件是 Go 程序對 Go 語言版的 DataItem 進行序列化的結果,並且該文件中的位元組被反序列化以產生一個 Java 語言的 DataItem 實例。Java 測試的輸出與 Go 測試的輸出相同。

用 numPairs 程序來結束

讓我們以一個示例作為結尾,來突出 Protobuf 效率,但又強調在任何編碼技術中都會涉及到的成本。考慮以下 Protobuf IDL 文件:

syntax = "proto3";
package main;

message NumPairs {
  repeated NumPair pair = 1;
}

message NumPair {
  int32 odd = 1;
  int32 even = 2;
}

NumPair 消息由兩個 int32 值以及每個欄位的整數標籤組成。NumPairs 消息是嵌入的 NumPair 消息的序列。

Go 語言的 numPairs 程序(如下)創建了 200 萬個 NumPair 實例,每個實例都附加到 NumPairs 消息中。該消息可以按常規方式進行序列化和反序列化。

示例 2、numPairs 程序

package main

import (
   "math/rand"
   "time"
   "encoding/xml"
   "encoding/json"
   "io/ioutil"
   "github.com/golang/protobuf/proto"
)

// protoc-generated code: start
var _ = proto.Marshal
type NumPairs struct {
   Pair []*NumPair `protobuf:"bytes,1,rep,name=pair" json:"pair,omitempty"`
}

func (m *NumPairs) Reset()         { *m = NumPairs{} }
func (m *NumPairs) String() string { return proto.CompactTextString(m) }
func (*NumPairs) ProtoMessage()    {}
func (m *NumPairs) GetPair() []*NumPair {
   if m != nil { return m.Pair }
   return nil
}

type NumPair struct {
   Odd  int32 `protobuf:"varint,1,opt,name=odd" json:"odd,omitempty"`
   Even int32 `protobuf:"varint,2,opt,name=even" json:"even,omitempty"`
}

func (m *NumPair) Reset()         { *m = NumPair{} }
func (m *NumPair) String() string { return proto.CompactTextString(m) }
func (*NumPair) ProtoMessage()    {}
func init() {}
// protoc-generated code: finish

var numPairsStruct NumPairs
var numPairs = &numPairsStruct

func encodeAndserialize() {
   // XML encoding
   filename := "./pairs.xml"
   bytes, _ := xml.MarshalIndent(numPairs, "", " ")
   ioutil.WriteFile(filename, bytes, 0644)

   // JSON encoding
   filename = "./pairs.json"
   bytes, _ = json.MarshalIndent(numPairs, "", " ")
   ioutil.WriteFile(filename, bytes, 0644)

   // ProtoBuf encoding
   filename = "./pairs.pbuf"
   bytes, _ = proto.Marshal(numPairs)
   ioutil.WriteFile(filename, bytes, 0644)
}

const HowMany = 200 * 100  * 100 // two million

func main() {
   rand.Seed(time.Now().UnixNano())

   // uncomment the modulus operations to get the more efficient version
   for i := 0; i < HowMany; i++ {
      n1 := rand.Int31() // % 2047
      if (n1 & 1) == 0 { n1++ } // ensure it&apos;s odd
      n2 := rand.Int31() // % 2047
      if (n2 & 1) == 1 { n2++ } // ensure it&apos;s even

      next := &NumPair {
                 Odd:  n1,
                 Even: n2,
              }
      numPairs.Pair = append(numPairs.Pair, next)
   }
   encodeAndserialize()
}

每個 NumPair 中隨機生成的奇數和偶數值的範圍在 0 到 20 億之間變化。就原始數據(而非編碼數據)而言,Go 程序中生成的整數總共為 16MB:每個 NumPair 為兩個整數,總計為 400 萬個整數,每個值的大小為四個位元組。

為了進行比較,下表列出了 XML、JSON 和 Protobuf 編碼的示例 NumsPairs 消息的 200 萬個 NumPair 實例。原始數據也包括在內。由於 numPairs 程序生成隨機值,因此樣本運行的輸出有所不同,但接近表中顯示的大小。

編碼 文件 位元組大小 Pbuf/其它 比例
pairs.raw 16MB 169%
Protobuf pairs.pbuf 27MB
JSON pairs.json 100MB 27%
XML pairs.xml 126MB 21%

表 2. 16MB 整數的編碼開銷

不出所料,Protobuf 和之後的 XML 和 JSON 差別明顯。Protobuf 編碼大約是 JSON 的四分之一,是 XML 的五分之一。但是原始數據清楚地表明 Protobuf 也會產生編碼開銷:序列化的 Protobuf 消息比原始數據大 11MB。包括 Protobuf 在內的任何編碼都涉及結構化數據,這不可避免地會增加位元組。

序列化的 200 萬個 NumPair 實例中的每個實例都包含個整數值:Go 結構中的 EvenOdd 欄位分別一個,而 Protobuf 編碼中的每個欄位、每個標籤一個。對於原始數據(而不是編碼數據),每個實例將達到 16 個位元組,樣本 NumPairs 消息中有 200 萬個實例。但是 Protobuf 標記(如 NumPair 欄位中的 int32 值)使用 varint 編碼,因此位元組長度有所不同。特別是,小的整數值(在這種情況下,包括標籤在內)需要不到四個位元組進行編碼。

如果對 numPairs 程序進行了修改,以使兩個 NumPair 欄位的值小於 2048,且其編碼為一或兩個位元組,則 Protobuf 編碼將從 27MB 下降到 16MB,這正是原始數據的大小。下表總結了樣本運行中的新編碼大小。

編碼 文件 位元組大小 Pbuf/其它 比例
None pairs.raw 16MB 100%
Protobuf pairs.pbuf 16MB
JSON pairs.json 77MB 21%
XML pairs.xml 103MB 15%

表 3. 編碼 16MB 的小於 2048 的整數

總之,修改後的 numPairs 程序的欄位值小於 2048,可減少原始數據中每個四位元組整數值的大小。但是 Protobuf 編碼仍然需要標籤,這些標籤會在 Protobuf 消息中添加位元組。Protobuf 編碼確實會增加消息大小,但是如果要編碼相對較小的整數值(無論是欄位還是鍵),則可以通過 varint 因子來減少此開銷。

對於包含混合類型的結構化數據(且整數值相對較小)的中等大小的消息,Protobuf 明顯優於 XML 和 JSON 等選項。在其他情況下,數據可能不適合 Protobuf 編碼。例如,如果兩個應用程序需要共享大量文本記錄或大整數值,則可以採用壓縮而不是編碼技術。

via: https://opensource.com/article/19/10/protobuf-data-interchange

作者:Marty Kalin 選題:lujun9972 譯者:wxy 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國