Protobuf學(xué)習(xí)記錄

簡(jiǎn)介

protoBuf是google 的一種數(shù)據(jù)交換的格式,它獨(dú)立于語言赊瞬,獨(dú)立于平臺(tái)诈胜。google 提供了多種語言的實(shí)現(xiàn):java、c#顾瞻、c++、go 和 python德绿,每一種實(shí)現(xiàn)都包含了相應(yīng)語言的編譯器以及庫文件荷荤。由于它是一種二進(jìn)制的格式,比使用 xml 進(jìn)行數(shù)據(jù)交換快許多移稳。

優(yōu)點(diǎn)

與同類型的數(shù)據(jù)交換格式相比(諸如json蕴纳,xml),由于protobuf是基于二進(jìn)制數(shù)據(jù)傳輸格式个粱,因此它具有高效的解析速度和更小的體積古毛,并且由于它是直接基于.proto文件生成對(duì)應(yīng)語言的數(shù)據(jù)結(jié)構(gòu),因此它的轉(zhuǎn)換過程更加簡(jiǎn)單直接。同時(shí)因?yàn)閜rotobuf實(shí)現(xiàn)都包含了相應(yīng)語言的編譯器以及庫文件稻薇,它將比傳統(tǒng)的基于一套規(guī)范解析更加精準(zhǔn)可控嫂冻,誤解的概率更低。

綜上優(yōu)點(diǎn) :

  • 高效的解析速度
  • 小巧的傳輸體積
  • 直接上手塞椎,簡(jiǎn)單易用
  • 解析可控桨仿,誤解率低

缺點(diǎn)

與傳統(tǒng)的數(shù)據(jù)交換格式相比,由于基于二進(jìn)制數(shù)據(jù)傳輸格式案狠,protobuf可讀性為零服傍。同時(shí)雖然protobuf強(qiáng)調(diào)的是跨平臺(tái)性,但相較于json莺戒,xml來說伴嗡,protobuf的語言覆蓋率偏低,并且手動(dòng)開發(fā)一個(gè)自定義protobuf的工作量也偏大瘪校。

綜上缺點(diǎn) :

  • 可讀性低
  • 語言支持相對(duì)少

用途

作為一種效率和兼容性都很優(yōu)秀的二進(jìn)制數(shù)據(jù)傳輸格式,可以用于分布式應(yīng)用之間的數(shù)據(jù)通信或者異構(gòu)環(huán)境[1]名段,并且在傳統(tǒng)的cs架構(gòu)的環(huán)境中阱扬,protobuf也可以用作客戶端服務(wù)端公用的數(shù)據(jù)交換格式。


簡(jiǎn)單上手

下面介紹在Windows下使用protobuf進(jìn)行python與java互通的helloword程序

下載protobuf

由于國內(nèi)google被墻伸辟,只能在github上下載下載地址
在windows下載protoc-XXX-win32.zip解壓即可

配置一個(gè).proto

//文件名test.proto
//定義包名
package test;
//protobuf的編譯器版本
syntax = "proto3";

//java的包名
option java_package = "test.protobuf";
//java中主類的名稱(及public修飾的類的名稱)
option java_outer_classname = "MProtobuf";


message request{

    //1為msg在request中的field_num麻惶,在后面會(huì)講
    string msg = 1;
    string commet = 2;
}

生成對(duì)應(yīng)的Java和Python文件

將剛才寫好的test.proto移到解壓后文件的bin目錄
并在該目錄下打開命令窗口。
輸入:

    protoc.exe --java_out=./ test.proto

在當(dāng)前目錄生成java文件

輸入:

    protoc.exe --python_out=./ test.proto

在當(dāng)前目錄生成python文件

在當(dāng)前目錄下test文件就是我們生成的java文件信夫,點(diǎn)進(jìn)去可見到MProtobuf.java
這就是我們?cè)冢?/p>

    option java_outer_classname = "MProtobuf";

定義的outer_classname

分析生成文件結(jié)構(gòu)

MProtobuf由requestOrBuilder和request構(gòu)成

  • 我們可以通過request中的newBuilder()構(gòu)建一個(gè)requestOrBuilder
    并通過requestOrBuilder構(gòu)建一個(gè)request
  • 在request中有writeTo方法:
public void writeTo(com.google.protobuf.CodedOutputStream output)
                        throws java.io.IOException {
      if (!getMsgBytes().isEmpty()) {
        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, msg_);
      }
      if (id_ != 0) {
        output.writeInt32(2, id_);
      }
      unknownFields.writeTo(output);
    }

能方便將request寫進(jìn)流里

  • 通過request中的parseFrom方法:
public static test.protobuf.HelloWorldProto.HelloWorld parseFrom(
        com.google.protobuf.CodedInputStream input)
        throws java.io.IOException {
      return com.google.protobuf.GeneratedMessageV3
          .parseWithIOException(PARSER, input);
    }

能方便將流轉(zhuǎn)換成request對(duì)象

編寫Server.java

  • 首先生成的MProtobuf.java是依賴于com.google.protobuf的窃蹋,所有先下載它的jar包

編寫Server.java:

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

import com.google.protobuf.CodedInputStream;

import test.protobuf.MProtobuf.request;

public class Server {
    public static void main(String[] args) throws IOException {
        
        ServerSocket server = new ServerSocket(12345);
        
        for(;;){
            Socket socket = server.accept();
            
            InputStream ism = socket.getInputStream();
            CodedInputStream csm = CodedInputStream.newInstance(ism);
            
            request req = request.parseFrom(csm);
            
            System.out.println(req);
        }
    }
}

編寫client.py

同樣在python中依賴protobuf

    pip install protobuf

在生成的test_pb2.py的文件下建立client.py

編寫client.py:

import test_pb2 as tp
import socket

socket = socket.socket()

socket.connect(("127.0.0.1",12345))

req = tp.request()
req.msg = "helloworld"
req.commet = "power by protobuf"
socket.sendall(req.SerializeToString())

運(yùn)行

先運(yùn)行Java,在運(yùn)行client.py
控制臺(tái)輸出:

msg: "helloworld"
commet: "power by protobuf"

運(yùn)行成功

深入

字段修飾符[2]

required
: 對(duì)于required的字段而言静稻,字段初值是必須要提供的警没,否則字段的便是未初始化的
: 對(duì)于修飾符為required的字段,序列化的時(shí)候必須給予初始化,否則程序運(yùn)行會(huì)異常

optional
: 對(duì)于optional的字段而言振湾,如果未進(jìn)行初始化杀迹,那么一個(gè)默認(rèn)值將賦予該字段編號(hào)
: 也可以指定默認(rèn)值,如下示例所示.

    optional string name = 1[default = "ssochi"]

repeated
: 對(duì)于repeated的字段而言,該字段可以重復(fù)多個(gè)押搪,即每個(gè)編碼單元可能有多個(gè)該字段
: 在高級(jí)語言里面树酪,我們可以通過數(shù)組來實(shí)現(xiàn),而在proto定義文件中可以使用repeated來修飾大州,從而達(dá)到相同目的续语。當(dāng)然,出現(xiàn)0次也是包含在內(nèi)的摧茴。

字段類型

.proto Type Java Type notes
int32 int 使用變長編碼绵载,在值為負(fù)數(shù)的情況下效率低,應(yīng)采用sin32代替
int64 long 使用變長編碼苛白,在值為負(fù)數(shù)的情況下效率低娃豹,應(yīng)采用sin64代替
uint32 int 使用變長編碼
uint64 long 使用變長編碼
sint32 int 使用變長編碼,有符號(hào)整型购裙,在值為負(fù)數(shù)的情況下效率高
sint64 long 使用變長編碼懂版,有符號(hào)整型,在值為負(fù)數(shù)的情況下效率高
fixed32 int 固定四個(gè)字節(jié).如果值經(jīng)常大于2^28使用fixed32會(huì)比使用uint32更高效
fixed64 long 固定八個(gè)字節(jié).如果值經(jīng)常大于2^56使用fixed64會(huì)比使用uint64更高效
sfixed32 int 有符號(hào)躏率,固定四個(gè)字節(jié)
sfixed64 long 有符號(hào)躯畴,固定八個(gè)字節(jié)
bool boolean
double double
string String string必須一致包含UTF-8編碼或者7-bit ASCII字符串
bytes ByteString 可以包含任意字節(jié)序列

字段類型對(duì)應(yīng)二進(jìn)制類型

字段類型 二進(jìn)制類型 二進(jìn)制編碼值
int32,int64,uint32,uint64,sint32,sint64,bool,enum Varint(可變長度int) 0
fixed64,sfixed64,double double 1
string,bytes,inner messages(內(nèi)部嵌套),packaed repeated fields(repeated字段) Length-delimited 2
groups(deprecated) Start group 3
groups(deprecated) Endd group 4
fixed32,sfixed32,float 32bit固定長度 5

field_num

每個(gè)消息的字段都有一個(gè)唯一的數(shù)字標(biāo)簽,這些標(biāo)簽用來表示你的字段在二進(jìn)制消息(message binary format)中處的位置。并且一旦指定標(biāo)簽號(hào)薇芝,在使用過程中是不可以更改的,標(biāo)記這些標(biāo)簽號(hào)在1-15的范圍內(nèi)每個(gè)字段需要使用1個(gè)字節(jié)用來編碼這一個(gè)字節(jié)包括字段所在的位置和字段的類型蓬抄。標(biāo)簽號(hào)在16-2047需要使用2個(gè)字節(jié)來編碼。所以你最好將1-15的標(biāo)簽號(hào)為頻繁使用到的字段所保留夯到。如果將來可能會(huì)添加一些頻繁使用到的元素嚷缭,記得留下一些1-15標(biāo)簽號(hào)。
最小可指定的標(biāo)簽號(hào)為1耍贾,最大的標(biāo)簽號(hào)為2^29 - 1或者536870911阅爽。不能使用19000-19999的標(biāo)簽號(hào)這些標(biāo)簽號(hào)是為protobuf內(nèi)部實(shí)現(xiàn)所保留的,如果你在.proto文件內(nèi)使用了這些標(biāo)簽號(hào)Protobuf編譯器將會(huì)報(bào)錯(cuò)荐开!

結(jié)構(gòu)

消息

protobuf中使用message定義一個(gè)消息付翁,在同一個(gè)包內(nèi)能直接引用,在不同包內(nèi)則需要先import

message request{
    string msg = 1;
    string commet = 2;
}

枚舉

enum PhoneType //枚舉消息類型
    {
        MOBILE = 0; //proto3版本中晃听,首成員必須為0百侧,成員不應(yīng)有相同的值
        HOME = 1;
        WORK = 2;
    }

Map

protobuf v2是不支持Map數(shù)據(jù)結(jié)構(gòu)的,官方給出的不就方法是通過如下代碼代替Map:

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

官方給出的解釋是:The map syntax is equivalent to the following on the wire, so protocol buffers implementations that do not support maps can still handle your data

在protobuf v3 的較新版本已經(jīng)支持Map[3]能扒,可以通過如下代碼申明Map

 map<string, string> values= 1;

嵌套

  • message定義中可以嵌套定義message,enum
  • 嵌套定義的單元外部可見佣渴,引用路徑由外到內(nèi)逐層引用

擴(kuò)展字段

protobuf通過extension解決數(shù)據(jù)結(jié)構(gòu)之間的不能派生的問題,以此來達(dá)到減少重復(fù)工作量和便于維護(hù)代碼的目的赫粥。

message BaseDataType{
     extensions 100 to max;   //標(biāo)識(shí)此字段可擴(kuò)展观话,此處可指定擴(kuò)展字段的ID有效范圍,to max表示字段范圍至最大值
     optional string field1 = 1;
     optional string field2 = 2;
}
message ExtendDataType{
     extend BaseDataType{
         optional ExtendDataType extendData = 100;     
         optional int32  extendValue = 101;          
     }
     optional string extendField1 = 1;
     optional string extendField2= 2;
} 

編碼

Varint編碼規(guī)則

Varints是將一個(gè)整數(shù)序列化為一個(gè)或多個(gè)Bytes的方法,越小的整數(shù)越平,使用的Bytes越少
: 每個(gè)byte最高位(msb)是標(biāo)志位,0表示是最后一個(gè)byte,1表示該字段值還有后續(xù)byte
: 每個(gè)byte低7位存放數(shù)值
: Varints使用Little Endian(小端)字節(jié)序

編碼試?yán)?/em>:

舉例: 300 的二進(jìn)制為 10 0101100

第一位:1(有后續(xù)) + 0101100
第二位:0(無后續(xù)) + 0000010

最終結(jié)果: 101011000000010

可見频蛔,由于是小端字節(jié)序,值越小Varints序列化后使用的Bytes越少秦叛。
然而負(fù)數(shù)在補(bǔ)碼的首位是1晦溪,則使用Varint序列化后,所有負(fù)數(shù)都相當(dāng)于非常大的數(shù)挣跋,
這造成了負(fù)數(shù)序列化后使用的bytes增加三圆,為了解決這個(gè)問題對(duì)于負(fù)數(shù)采用sint32或sint64。
而sint先采用Zigzag方法避免上述問題再通過Varint序列化。

Zigzag編碼規(guī)則

對(duì)于sint32采用(n<<1)^(n>>31)
對(duì)于sint32采用(n<<1)^(n>>63)

其中>>操作當(dāng)操作數(shù)為負(fù)數(shù)時(shí)高位補(bǔ)1正數(shù)時(shí)高位補(bǔ)1

Zigzag編碼將整數(shù)重新映射到定義域舟肉,使得整數(shù)映射后的補(bǔ)碼長度和絕對(duì)值的大小成正相關(guān):

原始值 編碼后的值 編碼后的補(bǔ)碼
0 0 00000000
-1 1 00000001
1 2 00000010
-2 3 00000011
3 4 00000100
... ... ...

length-delimited編碼

string,bytes都屬于length-delimited編碼,length-delimited(wire_type=2)的編碼方式:key+length+content

  • key的編碼方式是統(tǒng)一的
  • length采用varints編碼方式
  • content就是由length指定的長度的Bytes

消息編碼規(guī)則

  • message都是以一組或多組key-value對(duì)組成,key和value分別采用不同的編碼方式
  • 序列化時(shí)修噪,將message中所有key-value序列化成二進(jìn)制字節(jié)流。反序列化時(shí)路媚,解析出所有key-value對(duì)黄琼,
    如果遇到無法識(shí)別的類型,則直接跳過整慎。這種機(jī)制保證了舊有的編/解碼在協(xié)議添加新的字段時(shí)脏款,依舊可以正常工作
  • key由兩部分組成,一部分是在定義消息時(shí)對(duì)字段的編號(hào)(field_num[4])裤园,另一部分是字段類型(wire_type撤师,編號(hào)最大不超過536870911.
  • key編碼方式filed_num<<3|wire_type,編碼后的二進(jìn)制長度是變長的
  • value編碼則根據(jù)字段類型進(jìn)行編碼.

生成代碼分析

接下來我將通過protoc.exe生成的java文件,分析protobuf的編解碼過程
首先我們先創(chuàng)建一個(gè)包含protobuf所有類型的.proto

//文件名protobufStruct.proto

//protobuf 版本
syntax = "proto3";

//java的包名
option java_package = "struct.protobuf";
//java中主類的名稱(及public修飾的類的名稱)
option java_outer_classname = "structs";

//基本類型
message baseStruct{

    int32 int32value = 1;
    int64 int64value = 2; 
    uint32 uint32value = 3;
    uint64 uint64value = 4;
    sint32 sint32value = 5;
    sint64 sint64value = 6;
    fixed32 fixed32value = 7;
    fixed64 fixed64value = 8;
    sfixed32 sfixed32value = 9;
    sfixed64 sfixed64value = 10;
    bool boolvalue = 11;
    double doublevalue = 12;
    string stringvalue = 13;
    bytes bytesvalue = 14;
    //枚舉
    enum PhoneType{ 
        MOBILE = 0; 
        HOME = 1;
        WORK = 2;
    }

}
//復(fù)合類型
message complexStruct{
    //map
    map<int32, string> mapvalues= 1;
    //list
    repeated MapFieldEntry map_field = 2;
}
message MapFieldEntry {
  int32 key = 1;
  string value = 2;
}

通過執(zhí)行cmd指令生成java文件

    protoc.exe --java_out=./ protobufStruct.proto

我們可以看到baseStructOrBuilder:

 public interface baseStructOrBuilder extends
      // @@protoc_insertion_point(interface_extends:baseStruct)
      com.google.protobuf.MessageOrBuilder {

    /**
     * <code>int32 int32value = 1;</code>
     */
    int getInt32Value();

    /**
     * <code>int64 int64value = 2;</code>
     */
    long getInt64Value();

    /**
     * <code>uint32 uint32value = 3;</code>
     */
    int getUint32Value();

    /**
     * <code>uint64 uint64value = 4;</code>
     */
    long getUint64Value();

    /**
     * <code>sint32 sint32value = 5;</code>
     */
    int getSint32Value();

    /**
     * <code>sint64 sint64value = 6;</code>
     */
    long getSint64Value();

    /**
     * <code>fixed32 fixed32value = 7;</code>
     */
    int getFixed32Value();

    /**
     * <code>fixed64 fixed64value = 8;</code>
     */
    long getFixed64Value();

    /**
     * <code>sfixed32 sfixed32value = 9;</code>
     */
    int getSfixed32Value();

    /**
     * <code>sfixed64 sfixed64value = 10;</code>
     */
    long getSfixed64Value();

    /**
     * <code>bool boolvalue = 11;</code>
     */
    boolean getBoolvalue();

    /**
     * <code>double doublevalue = 12;</code>
     */
    double getDoublevalue();

    /**
     * <code>string stringvalue = 13;</code>
     */
    java.lang.String getStringvalue();
    /**
     * <code>string stringvalue = 13;</code>
     */
    com.google.protobuf.ByteString
        getStringvalueBytes();

    /**
     * <code>bytes bytesvalue = 14;</code>
     */
    com.google.protobuf.ByteString getBytesvalue();

    /**
     * <code>.baseStruct.PhoneType type = 15;</code>
     */
    int getTypeValue();
    /**
     * <code>.baseStruct.PhoneType type = 15;</code>
     */
    struct.protobuf.structs.baseStruct.PhoneType getType();
  }

這里java類型與protobuf的類型關(guān)系完全復(fù)合上面的字段類型表

將message序列化使用writeTo方法:

public void writeTo(com.google.protobuf.CodedOutputStream output)
                        throws java.io.IOException {
      if (int32Value_ != 0) {
        output.writeInt32(1, int32Value_);
      }
      if (int64Value_ != 0L) {
        output.writeInt64(2, int64Value_);
      }
      if (uint32Value_ != 0) {
        output.writeUInt32(3, uint32Value_);
      }
      if (uint64Value_ != 0L) {
        output.writeUInt64(4, uint64Value_);
      }
      if (sint32Value_ != 0) {
        output.writeSInt32(5, sint32Value_);
      }
      if (sint64Value_ != 0L) {
        output.writeSInt64(6, sint64Value_);
      }
      if (fixed32Value_ != 0) {
        output.writeFixed32(7, fixed32Value_);
      }
      if (fixed64Value_ != 0L) {
        output.writeFixed64(8, fixed64Value_);
      }
      if (sfixed32Value_ != 0) {
        output.writeSFixed32(9, sfixed32Value_);
      }
      if (sfixed64Value_ != 0L) {
        output.writeSFixed64(10, sfixed64Value_);
      }
      if (boolvalue_ != false) {
        output.writeBool(11, boolvalue_);
      }
      if (doublevalue_ != 0D) {
        output.writeDouble(12, doublevalue_);
      }
      if (!getStringvalueBytes().isEmpty()) {
        com.google.protobuf.GeneratedMessageV3.writeString(output, 13, stringvalue_);
      }
      if (!bytesvalue_.isEmpty()) {
        output.writeBytes(14, bytesvalue_);
      }
      if (type_ != struct.protobuf.structs.baseStruct.PhoneType.MOBILE.getNumber()) {
        output.writeEnum(15, type_);
      }
      unknownFields.writeTo(output);
    }

可以看出拧揽,當(dāng)一個(gè)值為空剃盾,直接跳過這個(gè)值。如果這個(gè)值存在强法,則寫入它的filed_num和它編碼后的值万俗,
前面講過對(duì)于不同類型的值,有不同的編碼方式饮怯,比如int32使用先Zigzag編碼再Varint編碼闰歪,
而string通過length-delimited編碼,等。

再看看complexStruct的builder和WriteTo方法

public interface complexStructOrBuilder extends
      // @@protoc_insertion_point(interface_extends:complexStruct)
      com.google.protobuf.MessageOrBuilder {

    /**
     * <code>map&lt;int32, string&gt; mapvalues = 1;</code>
     */
    int getMapvaluesCount();
    /**
     * <code>map&lt;int32, string&gt; mapvalues = 1;</code>
     */
    boolean containsMapvalues(
        int key);
    /**
     * Use {@link #getMapvaluesMap()} instead.
     */
    @java.lang.Deprecated
    java.util.Map<java.lang.Integer, java.lang.String>
    getMapvalues();
    /**
     * <code>map&lt;int32, string&gt; mapvalues = 1;</code>
     */
    java.util.Map<java.lang.Integer, java.lang.String>
    getMapvaluesMap();
    /**
     * <code>map&lt;int32, string&gt; mapvalues = 1;</code>
     */

    java.lang.String getMapvaluesOrDefault(
        int key,
        java.lang.String defaultValue);
    /**
     * <code>map&lt;int32, string&gt; mapvalues = 1;</code>
     */

    java.lang.String getMapvaluesOrThrow(
        int key);

    /**
     * <code>repeated .MapFieldEntry map_field = 2;</code>
     */
    java.util.List<struct.protobuf.structs.MapFieldEntry> 
        getMapFieldList();
    /**
     * <code>repeated .MapFieldEntry map_field = 2;</code>
     */
    struct.protobuf.structs.MapFieldEntry getMapField(int index);
    /**
     * <code>repeated .MapFieldEntry map_field = 2;</code>
     */
    int getMapFieldCount();
    /**
     * <code>repeated .MapFieldEntry map_field = 2;</code>
     */
    java.util.List<? extends struct.protobuf.structs.MapFieldEntryOrBuilder> 
        getMapFieldOrBuilderList();
    /**
     * <code>repeated .MapFieldEntry map_field = 2;</code>
     */
    struct.protobuf.structs.MapFieldEntryOrBuilder getMapFieldOrBuilder(
        int index);
  }

可以看出protobuf中的repected對(duì)應(yīng)java.util.list
map對(duì)應(yīng)java.util.Map

writeTo方法:

public void writeTo(com.google.protobuf.CodedOutputStream output)
                        throws java.io.IOException {
      com.google.protobuf.GeneratedMessageV3
        .serializeIntegerMapTo(
          output,
          internalGetMapvalues(),
          MapvaluesDefaultEntryHolder.defaultEntry,
          1);
      for (int i = 0; i < mapField_.size(); i++) {
        output.writeMessage(2, mapField_.get(i));
      }
      unknownFields.writeTo(output);
    }

可以看出蓖墅,被repected修飾的字段库倘,這個(gè)字段中每個(gè)子項(xiàng)都用相同的field_num。
這樣的優(yōu)點(diǎn)是不用特殊標(biāo)識(shí)就能標(biāo)識(shí)一個(gè)集合字段论矾。
但它的缺點(diǎn)很明顯教翩,整個(gè)protobuf只能標(biāo)識(shí)一種集合類型。
也就是說map肯定轉(zhuǎn)成list然后再編碼的

protected static <V> void serializeIntegerMapTo(
      CodedOutputStream out,
      MapField<Integer, V> field,
      MapEntry<Integer, V> defaultEntry,
      int fieldNumber) throws IOException {
    Map<Integer, V> m = field.getMap();
    if (!out.isSerializationDeterministic()) {
      serializeMapTo(out, m, defaultEntry, fieldNumber);
      return;
    }
    // Sorting the unboxed keys and then look up the values during serialziation is 2x faster
    // than sorting map entries with a custom comparator directly.
    int[] keys = new int[m.size()];
    int index = 0;
    for (int k : m.keySet()) {
      keys[index++] = k;
    }
    Arrays.sort(keys);
    for (int key : keys) {
      out.writeMessage(fieldNumber,
          defaultEntry.newBuilderForType()
              .setKey(key)
              .setValue(m.get(key))
              .build());
    }
  }
 /** Serialize the map using the iteration order. */
private static <K, V> void serializeMapTo(
      CodedOutputStream out,
      Map<K, V> m,
      MapEntry<K, V> defaultEntry,
      int fieldNumber)
      throws IOException {
    for (Map.Entry<K, V> entry : m.entrySet()) {
      out.writeMessage(fieldNumber,
          defaultEntry.newBuilderForType()
              .setKey(entry.getKey())
              .setValue(entry.getValue())
              .build());
    }

通過serializeIntegerMapTo和serializeMapTo方法可知:
當(dāng)Map最終是轉(zhuǎn)換成list來編碼的贪壳,其中map轉(zhuǎn)換為list有兩種順序饱亿,
一種是按照map的迭代順序存入list
一種是按照key的字典序

綜上,我們通過分析代碼理解了protobuf的編碼過程闰靴,當(dāng)然解碼過程其實(shí)也就是把編碼過程倒過來彪笼。
解碼需要注意的是zigzag解碼方式:
當(dāng)使用支持無符號(hào)移位的語言時(shí)可以使用:

    n = (n >>> 1)^(-(n & 1))

當(dāng)使用像python這類不支持無符號(hào)移位的語言可以使用:

    n = (n ^ (-(n & 1)) )>> 1

  1. 異構(gòu)網(wǎng)絡(luò)環(huán)境(Heterogeneous Network Environments)是指由不同制造商生產(chǎn)的計(jì)算機(jī)和系統(tǒng)組成的網(wǎng)絡(luò)環(huán)境。這些計(jì)算機(jī)系統(tǒng)運(yùn)行不同的操作系統(tǒng)和通信協(xié)議蚂且,想統(tǒng)一其計(jì)算機(jī)資源的機(jī)構(gòu)通常會(huì)面臨集成異種機(jī)系統(tǒng)的任務(wù)配猫。一般地,每一個(gè)部門或分部已經(jīng)根據(jù)操作系統(tǒng)杏死、局域網(wǎng)拓?fù)浣Y(jié)構(gòu)泵肄、通信協(xié)議捆交、應(yīng)用程序、電子函件系統(tǒng)以及其它因素規(guī)定了自己的網(wǎng)絡(luò)需要腐巢。企業(yè)網(wǎng)的目標(biāo)就是使這些分散的資源可以進(jìn)行互連和互操作品追,以便網(wǎng)絡(luò)用戶可以和其他用戶一起共享文件和電子函件,或者訪問企業(yè)的數(shù)據(jù)資源系忙。 ?

  2. 為了更好的跨語言性诵盼,google已將protobuf v2中的required刪去惠豺,默認(rèn)都是optional银还。并且也不在使用默認(rèn)值及[default = * ],更多詳見https://github.com/google/protobuf/blob/master/CHANGES.txt?

  3. 在知乎上看見一條關(guān)于protobuf v3中使用Map的警告:protobuf v3用了Emitter洁墙,Unity3d的il2cpp是不支持的蛹疯,用Unity的留心了。 ?

  4. field_num及為string name = 1其中1就是name的field_num热监。 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捺弦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子孝扛,更是在濱河造成了極大的恐慌列吼,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苦始,死亡現(xiàn)場(chǎng)離奇詭異寞钥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陌选,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門理郑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咨油,你說我怎么就攤上這事您炉。” “怎么了役电?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵赚爵,是天一觀的道長。 經(jīng)常有香客問我法瑟,道長冀膝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任瓢谢,我火速辦了婚禮畸写,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘氓扛。我一直安慰自己枯芬,他們只是感情好论笔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著千所,像睡著了一般狂魔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上淫痰,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天最楷,我揣著相機(jī)與錄音,去河邊找鬼待错。 笑死籽孙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的火俄。 我是一名探鬼主播犯建,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼瓜客!你這毒婦竟也來了适瓦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤谱仪,失蹤者是張志新(化名)和其女友劉穎玻熙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疯攒,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗦随,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卸例。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片称杨。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筷转,靈堂內(nèi)的尸體忽然破棺而出姑原,到底是詐尸還是另有隱情,我是刑警寧澤呜舒,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布锭汛,位于F島的核電站,受9級(jí)特大地震影響袭蝗,放射性物質(zhì)發(fā)生泄漏唤殴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一到腥、第九天 我趴在偏房一處隱蔽的房頂上張望朵逝。 院中可真熱鬧,春花似錦乡范、人聲如沸配名。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渠脉。三九已至宇整,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芋膘,已是汗流浹背鳞青。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留为朋,地道東北人臂拓。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像潜腻,于是被迫代替她去往敵國和親埃儿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348