簡(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<int32, string> mapvalues = 1;</code>
*/
int getMapvaluesCount();
/**
* <code>map<int32, string> 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<int32, string> mapvalues = 1;</code>
*/
java.util.Map<java.lang.Integer, java.lang.String>
getMapvaluesMap();
/**
* <code>map<int32, string> mapvalues = 1;</code>
*/
java.lang.String getMapvaluesOrDefault(
int key,
java.lang.String defaultValue);
/**
* <code>map<int32, string> 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
-
異構(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ù)資源系忙。 ?
-
為了更好的跨語言性诵盼,google已將protobuf v2中的required刪去惠豺,默認(rèn)都是optional银还。并且也不在使用默認(rèn)值及[default = * ],更多詳見https://github.com/google/protobuf/blob/master/CHANGES.txt。 ?
-
在知乎上看見一條關(guān)于protobuf v3中使用Map的警告:protobuf v3用了Emitter洁墙,Unity3d的il2cpp是不支持的蛹疯,用Unity的留心了。 ?
-
field_num及為
string name = 1
其中1就是name的field_num热监。 ?