序列化是將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進(jìn)制字節(jié)流的過(guò)程。
Protobuf對(duì)于不同的字段類型采用不同的編碼方式和數(shù)據(jù)存儲(chǔ)方式對(duì)消息字段進(jìn)行序列化,以確保得到高效緊湊的數(shù)據(jù)壓縮。
Protobuf序列化過(guò)程如下:
(1)判斷每個(gè)字段是否有設(shè)置值,有值才進(jìn)行編碼。
(2)根據(jù)字段標(biāo)識(shí)號(hào)與數(shù)據(jù)類型將字段值通過(guò)不同的編碼方式進(jìn)行編碼。
(3)將編碼后的數(shù)據(jù)塊按照字段類型采用不同的數(shù)據(jù)存儲(chǔ)方式封裝成二進(jìn)制數(shù)據(jù)流。
創(chuàng)新互聯(lián)公司2013年至今,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元沙灣做網(wǎng)站,已為上家服務(wù),為沙灣各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575
反序列化是將在序列化過(guò)程中所生成的二進(jìn)制字節(jié)流轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或者對(duì)象的過(guò)程。
Protobuf反序列化過(guò)程如下:
(1)調(diào)用消息類的parseFrom(input)解析從輸入流讀入的二進(jìn)制字節(jié)數(shù)據(jù)流。
(2)將解析出來(lái)的數(shù)據(jù)按照指定的格式讀取到Java、C++、Phyton對(duì)應(yīng)的結(jié)構(gòu)類型中。
Varint編碼是一種變長(zhǎng)的編碼方式,編碼原理是用字節(jié)表示數(shù)字,值越小的數(shù)字,使用越少的字節(jié)數(shù)表示。因此,可以通過(guò)減少表示數(shù)字的字節(jié)數(shù)進(jìn)行數(shù)據(jù)壓縮。
對(duì)int32類型的數(shù)字,一般需要4個(gè)字節(jié)表示。如果采用Varint編碼,對(duì)于很小的int32類型數(shù)字,則可以用1個(gè)字節(jié)來(lái)表示;雖然大的數(shù)字會(huì)需要5個(gè)字節(jié)來(lái)表示,但大多數(shù)情況下,消息都不會(huì)有很大的數(shù)字,所以采用Varint編碼方式總是可以用更少的字節(jié)數(shù)來(lái)表示數(shù)字。
Varint編碼后每個(gè)字節(jié)的最高位都有特殊含義:
A、如果是1,表示后續(xù)的字節(jié)也是數(shù)字的一部分。
B、如果是0,表示本字節(jié)是最后一個(gè)字節(jié),且剩余7位都用來(lái)表示數(shù)字。
當(dāng)使用Varint解碼時(shí)時(shí),只要讀取到最高位為0的字節(jié)時(shí),表示本字節(jié)是一個(gè)值經(jīng)Varint編碼后得到的字節(jié)流的最后一個(gè)字節(jié)。
在計(jì)算機(jī)內(nèi),負(fù)數(shù)一般會(huì)被表示為很大的整數(shù) ,因?yàn)橛?jì)算機(jī)定義負(fù)數(shù)的符號(hào)位為數(shù)字的最高位,如果采用Varint編碼方式表示一個(gè)負(fù)數(shù),那么一定需要5個(gè)byte(因?yàn)樨?fù)數(shù)的最高位是1,會(huì)被當(dāng)做很大的整數(shù)處理)
Protobuf定義了sint32 / sint64類型表示負(fù)數(shù),通過(guò)先采用Zigzag編碼(將有符號(hào)數(shù)轉(zhuǎn)換成無(wú)符號(hào)數(shù)),再采用Varint編碼,從而用于減少編碼后的字節(jié)數(shù)。
對(duì)于一個(gè)int32類型的值300的Varint編碼如下:
300的二進(jìn)制編碼為:100101100(256+32+8+4)
從字節(jié)流末尾取出7bit并在最高位增加1構(gòu)成一個(gè)字節(jié):[1]010 1100
從字節(jié)流末尾取出7bit并在最高位增加1構(gòu)成一個(gè)字節(jié),如果是最后一個(gè)字節(jié)增加0:[0]0000010
兩字節(jié)為:[0]0000010 [1]010 1100
轉(zhuǎn)換為小端模式:10101100 00000010
編碼結(jié)果:1010 1100 0000 0010
Zigazg編碼是一種變長(zhǎng)的編碼方式,其編碼原理是使用無(wú)符號(hào)數(shù)來(lái)表示有符號(hào)數(shù)字,使得絕對(duì)值小的數(shù)字都可以采用較少字節(jié)來(lái)表示,特別對(duì)表示負(fù)數(shù)的數(shù)據(jù)能更好地進(jìn)行數(shù)據(jù)壓縮。
Zigzag編碼對(duì)Varint編碼在表示負(fù)數(shù)時(shí)不足的補(bǔ)充,從而更好的幫助Protobuf進(jìn)行數(shù)據(jù)的壓縮。因此,如果提前預(yù)知字段值是可能取負(fù)數(shù)的時(shí)候,需要采用sint32/sint64數(shù)據(jù)類型。
Protobuf通過(guò)Varint和Zigzag編碼后,大大減少了字段值占用字節(jié)數(shù)。
-2的Zigzag過(guò)程如下:
T-L-V(Tag - Length - Value),即標(biāo)識(shí)符-長(zhǎng)度-字段值的存儲(chǔ)方式,其原理是以標(biāo)識(shí)符-長(zhǎng)度-字段值表示單個(gè)數(shù)據(jù),最終將所有數(shù)據(jù)拼接成一個(gè)字節(jié)流,從而實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)的功能。
其中Length可選存儲(chǔ),如儲(chǔ)存Varint編碼數(shù)據(jù)就不需要存儲(chǔ)Length,此時(shí)為T(mén)-V存儲(chǔ)方式。
T-L-V?存儲(chǔ)方式的優(yōu)點(diǎn):
A、不需要分隔符就能分隔開(kāi)字段,減少了分隔符的使用。
B、各字段存儲(chǔ)得非常緊湊,存儲(chǔ)空間利用率非常高。
C、如果某個(gè)字段沒(méi)有被設(shè)置字段值,那么該字段在序列化時(shí)的數(shù)據(jù)中是完全不存在的,即不需要編碼,相應(yīng)字段在解碼時(shí)才會(huì)被設(shè)置為默認(rèn)值。
消息字段的標(biāo)識(shí)號(hào)、數(shù)據(jù)類型、字段值經(jīng)過(guò)Protobuf采用Varint和Zigzag編碼后,以T-V(Tag-Value)方式進(jìn)行數(shù)據(jù)存儲(chǔ)。
對(duì)于Varint與Zigzag編碼方式編碼的數(shù)據(jù),省略了T-L-V中的字節(jié)長(zhǎng)度Length。
Tag是消息字段標(biāo)識(shí)符和數(shù)據(jù)類型經(jīng)Varint與Zigzag編碼后的值,因此Tag存儲(chǔ)了字段的標(biāo)識(shí)符(field_number)和數(shù)據(jù)類型(wire_type),即Tag = 字段數(shù)據(jù)類型(wire_type) + 標(biāo)識(shí)號(hào)(field_number)。
Tag占用一個(gè)字節(jié)的長(zhǎng)度(如果標(biāo)識(shí)符大于15,則占用多一個(gè)字節(jié)的位置),字段數(shù)據(jù)類型(wire_type)占用3個(gè)bit,字段標(biāo)識(shí)符(field_number)占用4個(gè)bit,最高位用于Varint編碼保留。
Tag = (field_number << 3) | wire_type
enum WireType {
WIRETYPE_VARINT = 0,
WIRETYPE_FIXED64 = 1,
WIRETYPE_LENGTH_DELIMITED = 2,
WIRETYPE_START_GROUP = 3,
WIRETYPE_END_GROUP = 4,
WIRETYPE_FIXED32 = 5
};
解碼時(shí),Protobuf根據(jù)Tag將Value對(duì)應(yīng)于消息中的字段。
message person
{
required int32 id = 1;
// wire type = 0,field_number =1
required string name = 2;
// wire type = 2,field_number =2
}
對(duì)于Person消息的name字段的Tag編碼如下:
nameTag = 2 << 3 | 2
nameTag = 0001 0010
根據(jù)Tag解碼得到filed_number、wire_type:
nameTag = 0001 0010
field_number = nameTag >> 3
field_number = 0010
wire_type = nameTag & 3
wire_type = 010
Protobuf對(duì)于數(shù)據(jù)存儲(chǔ)的三大原則:
(1)Protocol Buffer將消息中的每個(gè)字段進(jìn)行編碼后,利用T - L - V?存儲(chǔ)方式進(jìn)行數(shù)據(jù)的存儲(chǔ),最終得到一個(gè)二進(jìn)制字節(jié)流。
(2)ProtoBuf對(duì)于不同數(shù)據(jù)類型采用不同的序列化方式(數(shù)據(jù)編碼方式與數(shù)據(jù)存儲(chǔ)方式)
Protobuf對(duì)于不同的字段類型采用不同的編碼和數(shù)據(jù)存儲(chǔ)方式對(duì)消息字段進(jìn)行序列化,以確保得到高效緊湊的數(shù)據(jù)壓縮。不同類型的數(shù)據(jù)采用的編碼方式和存儲(chǔ)方式如下:
對(duì)于Varint編碼數(shù)據(jù)的存儲(chǔ),不需要存儲(chǔ)字節(jié)長(zhǎng)度Length,使用T-V存儲(chǔ)方式進(jìn)行存儲(chǔ);對(duì)于采用其它編碼方式(如LENGTH_DELIMITED)的數(shù)據(jù),使用T-L-V存儲(chǔ)方式進(jìn)行存儲(chǔ)。
(3)ProtoBuf對(duì)于數(shù)據(jù)字段值的獨(dú)特編碼方式與T-L-V數(shù)據(jù)存儲(chǔ)方式,使得?ProtoBuf序列化后數(shù)據(jù)量體積極小。
WireType=0的類型包括int32,int64,uint32,unint64,bool,enum以及sint32和sint64。
編碼方式采用Varint編碼(如果為負(fù)數(shù),采用Zigzag輔助編碼),數(shù)據(jù)存儲(chǔ)方式使用T-V方式存儲(chǔ)二進(jìn)制字節(jié)流。
WireType=1的類型包括fixed64,sfixed64,double。
編碼方式采用64bit編碼(編碼后數(shù)據(jù)大小為64bit,高位在后,低位在前),數(shù)據(jù)存儲(chǔ)方式使用T-V方式存儲(chǔ)二進(jìn)制字節(jié)流。
WireType=2的類型包括string,bytes,嵌套消息,packed repeated字段。
對(duì)于編碼方式,標(biāo)識(shí)符Tag采用Varint編碼,字節(jié)長(zhǎng)度Length采用Varint編碼,string類型字段值采用UTF-8編碼,嵌套消息類型的字段值根據(jù)嵌套消息內(nèi)部的字段數(shù)據(jù)類型進(jìn)行選擇,
數(shù)據(jù)存儲(chǔ)方式使用T-L-V方式存儲(chǔ)二進(jìn)制字節(jié)流。
WireType=5的類型包括fixed32,sfixed32,float。
編碼方式采用32bit編碼(編碼后數(shù)據(jù)大小為32bit,高位在后,低位在前),數(shù)據(jù)存儲(chǔ)方式使用T-V方式存儲(chǔ)二進(jìn)制字節(jié)流。
String類型字段的值使用UTF-8編碼。消息數(shù)據(jù)流如下:
message Test
{
required string str = 2;
}
// 將str設(shè)置為:testing
Test.setStr(“testing”)
// 經(jīng)過(guò)protobuf編碼序列化后的數(shù)據(jù)以二進(jìn)制的方式輸出
// 輸出為:18, 7, 116, 101, 115, 116, 105, 110, 103
嵌套消息類型采用T-L-V的存儲(chǔ)方式,外部消息的V即為嵌套消息的字段?
,在T-L-V的V中嵌套了一系列的T-L-V。
編碼方式:字段值(即V)根據(jù)字段的數(shù)據(jù)類型采用不同編碼方式。
message Test2
{
required string str = 1;
required int32 id1 = 2;
}
message Test3 {
required Test2 c = 1;
}
// 將Test2中的字段str設(shè)置為:testing
// 將Test2中的字段id1設(shè)置為:296
// 編碼后的字節(jié)為:10 ,12 ,18,7,116, 101, 115, 116, 105, 110, 103,16,-88,2
message Test
{
repeated int32 Car = 4 ;
// 表達(dá)方式1:不帶packed=true
repeated int32 Car = 4 [packed=true];
// 表達(dá)方式2:帶packed=true
}
Test.setCar(3);
Test.setCar(270);
Test.setCar(86942);
如果序列化時(shí)對(duì)多個(gè) T - V對(duì)存儲(chǔ)(不帶packed=true),則會(huì)導(dǎo)致Tag的冗余,即相同的Tag存儲(chǔ)多次。
為了解決Tag數(shù)據(jù)冗余,采用帶packed=true的repeated字段存儲(chǔ)方式,即將相同的Tag只存儲(chǔ)一次、添加repeated字段下所有字段值的長(zhǎng)度Length、連續(xù)存儲(chǔ)repeated字段值,組成一個(gè)大的Tag - Length - Value -Value -Value對(duì),即T - L - V - V - V對(duì)。
通過(guò)采用帶packed=true?的?repeated字段存儲(chǔ)方式,從而更好地壓縮序列化后的數(shù)據(jù)長(zhǎng)度。
基于Protobuf序列化原理分析,為了有效降低序列化后數(shù)據(jù)量的大小,可以采用以下措施:
(1)多用?optional或?repeated修飾符?
若optional?或?repeated?字段沒(méi)有被設(shè)置字段值,那么該字段在序列化時(shí)的數(shù)據(jù)中是完全不存在的,即不需要進(jìn)行編碼,但相應(yīng)的字段在解碼時(shí)會(huì)被設(shè)置為默認(rèn)值。
(2)字段標(biāo)識(shí)號(hào)(Field_Number)盡量只使用1-15,且不要跳動(dòng)使用
Tag是需要占字節(jié)空間的。如果Field_Number>16時(shí),F(xiàn)ield_Number的編碼就會(huì)占用2個(gè)字節(jié),那么Tag在編碼時(shí)就會(huì)占用更多的字節(jié);如果將字段標(biāo)識(shí)號(hào)定義為連續(xù)遞增的數(shù)值,將獲得更好的編碼和解碼性能。
(3)若需要使用的字段值出現(xiàn)負(fù)數(shù),請(qǐng)使用sint32/sint64,不要使用int32/int64。
采用sint32/sint64數(shù)據(jù)類型表示負(fù)數(shù)時(shí),會(huì)先采用Zigzag編碼再采用Varint編碼,從而更加有效壓縮數(shù)據(jù)。
(4)對(duì)于repeated字段,盡量增加packed=true修飾
增加packed=true修飾,repeated字段會(huì)采用連續(xù)數(shù)據(jù)存儲(chǔ)方式,即T - L - V - V -V方式。
參考文獻(xiàn):
Carson_Ho:Protocol Buffer序列化原理大揭秘
分享名稱:gRPC快速入門(mén)(二)——Protobuf序列化原理解析
網(wǎng)站URL:http://vcdvsql.cn/article16/peppgg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供標(biāo)簽優(yōu)化、響應(yīng)式網(wǎng)站、App設(shè)計(jì)、動(dòng)態(tài)網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)、虛擬主機(jī)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)