日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ProtoBuf的使用以及原理分析

發布時間:2024/4/11 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ProtoBuf的使用以及原理分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Protocal Buffers(簡稱protobuf)是Google的一項技術,用于結構化的數據序列化、反序列化。

Protobuf的使用比較廣泛,常用于RPC 系統(Remote Procedure Call Protocol System)和持續數據存儲系統。其主要優點是空間開銷小和性能比較好,類似于XML生成和解析,但protobuf的效率高于XML,不過protobuf生成的是字節碼,可讀性比XML差。

相關官方文檔:

Protocol Buffers官網:https://developers.google.com/protocol-buffers/

Protocol Buffers官網(中文):https://developers.google.com/protocol-buffers/?hl=zh-CN

Git地址:https://github.com/google/protobuf

Java API:https://developers.google.com/protocol-buffers/docs/reference/java/?hl=zh-CN

proro文件的編寫指南:https://developers.google.com/protocol-buffers/docs/style?hl=zh-CN

Java使用指南:https://developers.google.com/protocol-buffers/docs/javatutorial?hl=zh-CN

一.protobuf的基本應用

使用protobuf開發的基本步驟為:

?1.配置開發環境 安裝protocol 代碼編譯器

?2.編寫對應的.proto文件,定義序列化對象結構

?3.使用編譯器生成對應的序列化工具類

?4.編寫自己的應用

使用github上的protoc-3.5.1-win32.zip?https://github.com/google/protobuf/releases

每個Protobuf 的 字段 都有一定的格式:

限定修飾符?| 數據類型 | 字段名稱?| = | 字段編碼值?| [字段默認值]

1.限定修飾符:?required/optional/repeated

Required:表示是一個必須字段,缺失該字段會引發編解碼異常,導致消息被丟棄。

Optional:表示是一個可選字段。

Repeated:表示該字段可重復,每次可以包含0~N個值,表示集合

2.數據類型

protobuf定義了一些基本的數據類型

string/bytes/bool/int32/int64/float/double

enum 枚舉類? ? ?message 自定義類

3.字段名稱

字段名稱的命名方式與C、Java等語言的變量命名方式幾乎是相同的。

protobuf建議字段的命名采用以下劃線分割的駝峰式。例如 建議使用first_name 而不是firstName.

4.字段編碼值

編碼值的取值范圍為 1~2^32(4294967296)。

相同的編碼值,其限定修飾符和數據類型必須相同。

消息中的字段的編碼值無需連續,只要是合法的,并且不能在同一個消息中有字段包含相同的編碼值。不過通常習慣使用連續的字段編碼值,比較易于理解

⑤.默認值。

在發送數據時,對于required數據類型,如果用戶沒有設置值,則使用默認值傳遞。當接受數據時,對于optional字段,如果沒有接收到對應值,則設置為默認值

以下是一個簡單的User對象的proto文件

syntax="proto2";option java_package = "com.chenpp.serializer.protobuf"; option java_outer_classname="UserProto"; message User {//1,2表示當前序列化后的字段順序required int32 age = 2; //年齡required string name = 1;//姓名}

使用proto自己的編譯器進行編譯,生成實體類

protoc.exe --java_out=./java? ?./user.proto?

–java_out 后面是生成java文件存放地址?
最后的參數是proto文件的名稱,可以寫絕對地址,也可以直接寫proto文件名稱

引入protobuf對應的dependency,打印對應的序列化結果:

<dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.5.0</version></dependency> public class ProtoSerializer implements ISerializer {public <T> byte[] serialize(T obj) {UserProto.User user = UserProto.User.newBuilder().setAge(21).setName("chenpp").build();return user.toByteArray();}public <T> T deserialize(byte[] data, Class<T> clazz) {try {UserProto.User user = UserProto.User.parseFrom(data);return (T) user;} catch (InvalidProtocolBufferException e) {e.printStackTrace();}return null;} }==================protobuf======================== protobuf {age:21}序列化后的byte[]的length: 10 10 6 99 104 101 110 112 112 16 21 name: "chenpp" age: 21

二.protobuf序列化的原理

之前那篇文章,講過Json里的序列化結果為: { "name":"chenpp","age":21}? -- 一共26個字節,而想要將其進行進一步壓縮,就需要去掉一些冗余的字節

?思路:1)能不能去掉定義屬性(約定1=name,2=age)? 約定了字段,約定了類型? 去除分隔符(引號,冒號,逗號之類的)

? ? ? ? ?2)壓縮數字,因為日常經常使用到的都是一些比較小的數字,一共int占4個字節,但實際有效的字節數沒有那么多

? ? ? ? ??英文轉化成數字(ASCII)并對數字進行壓縮

protobuf里使用到了兩種壓縮算法:varint和zigzag算法

varint算法

這是針對無符號整數,一種壓縮方式

壓縮方法:

1.對一個無符號整數,將其換算成二進制

2.從右往左取7,然后最高位1

3.一直繼續直到取到最后一個有意義的字節(在最后一個有意義的字節上最高位補0)

4.先取到的字節排在后到的字節前面,得到一個新的字節,轉換成十進制就是壓縮的結果

以:500為例:其實有意義的就是2個字節

?0000?0001 1111? 0100= 2^2+2^4+2^5+2^6+2^7+2^8 = 4+16+32+64+128+256 = 500

按照其壓縮方式得到的新的二進制字節為:

1111?0100? | 0000 0011

1111?0100 代表的是負數,使用補碼(正數取反+1),并且最高位符號位為1

轉化后: 先?-11111?0011? ?取反? 0000 1100 = 12

計算出來就是 -12 3

字符如何轉化成數字編碼

對于英文字母,這里的name字段,使用ASCII碼對照表查找對應的數字

chenpp: 對應的ASCII

c-99 ?h-104? e-101 ?n-110? p-112

按照varint的算法 取7位補最高位為1(最后一個字節最高位補0)

對于小于127(2^7-1=127)的數字,其有效字節只有1位,壓縮的時候最高位補0,故壓縮之前和壓縮之后的數字沒有變化

protobuf的存儲格式

protobuf采用T-L-V的格式進行存儲

[Tag | length | value ]

l其中length為可選, 但是string必須有length(這樣在反序列化的時候程序才知道該字符串從哪里開始到哪里結束), 而int是不需要length的

Tag:字段標識符,用于標識字段 其值等于

??? field_number(當前字段的編號,第幾個字段)<<3|wire_type(int64/int32/可變長度string)

Length:Value的字節長度 (string需要有,int不需要)

Value:消息字段經過編碼后的值

Age:int32? 2<<3|0 = 16

Name: string 1<<3|2 = 10?

在反序列化的時候根據tag?mod 8 的余數判斷對應的wireType,從而知道該字段對應的存儲方式和編碼方式

對應User(name="chenpp",age=21)按照varint進行壓縮后其序列化結果為:

c-99 ?h-104? e-101 ?n-110? p-112

String:tag-length-value?

Int32:tag-value

10??????? 6????????? 99????? 104????? 101??? 110?? 112?? 112????? 16???????? 21

Tag – length? -? c? -???? h???? –?? e??? -?? n?? -?? p?? -? p?? -? Tag -? Value

根據上述壓縮算法可知:對于int32/int64,value有且只有最后一個字節為正數,故當遇到第一個為正數的字節時就知道其value值已經獲取完畢,所以對于int32類型的字段,不需要length,只需要tag和value就足夠了

ZigZag算法

在計算機中,負數會被表示為很大的整數,因為負數的符號位在最高位,如果使用varint算法進行壓縮的話會需要 32/7 ~ 5字節,

加大了空間的開銷.故在protobuf中對于有符號整數會使用sint32/sint64來表示。protobuf中負數的壓縮方式是先使用ZigZag算符號數(無論數值是正數還是負數,都會進行一次壓縮計算)轉化為無符號數,再使用varint進行壓縮

ZigZag算法的思路:

負數之所以不好壓縮:一個原因是因為其最高位為1,另一個原因是對于絕對值比較小的負數,其正數會有很多的前導零,那么在使用補碼表示負數的時候(取反+1),會導致負數會有很多的前導1,使得無法壓縮

所以ZigZag采用的辦法就是:先將符號位從最高位移動到最低位,其余數字均往前移動1位;然后再對所有的數字(符號位除外)進行取反,這樣得到的計算結果就是一個可以壓縮的數字(符號位不占據最高位,而小絕對值的數值由于取反操作其前導1都變為了前導0)

比方說-300:

其對應的正數的原碼為: 0000 0000 0000 0000 0000 0001 0010 1100

取反: 1111 1111 1111 1111 1111 1110 1101 0011

+1:??? 1111 1111 1111 1111??1111 1110 1101 0100 (-300)

移動符號位之后: 1111 1111 1111 1111 1111 1101 1010 1001

取反:0000 0000 0000 0000 0000 0010 0101 0111

計算后為0010 0101 0111 = 599

在ZigZag算法里也是使用這種思路對有符號整數進行壓縮的,將其轉化成表達式就是

Sint32: (n<<1)^ (n>>31)

Sint64: (n<<1)^(n>>63)

當n為正數時,n>>31為0000 0000 0000 0000 0000 0000 0000 0000;當n為負數時,n>>31為1111 1111 1111 1111 1111 1111 1111 1111

(n>>31)與(n<<1)進行異或之后,如果n為正數,(n>>31)^(n<<1) =n<<1;如果n為負數,其計算結果和上述所說的最高位移動到最后,然后取反效果是一樣的(n<<1補的最低位為0和n>>31異或運算之后一定為1,而其他位上與1做異或運算相當于取反),這樣一來就可以使用varint進行壓縮計算了

對-300的ZigZag計算結果:599?使用varint算法進行壓縮

得到1101 0111?? 0000? 0100

其結果為:-41?? 4

到此,protobuf的壓縮原理就介紹完了

總結

以上是生活随笔為你收集整理的ProtoBuf的使用以及原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。