ProtoBuf 是google團(tuán)隊(duì)開(kāi)發(fā)的用于高效存儲(chǔ)和讀取結(jié)構(gòu)化數(shù)據(jù)的工具。什么是結(jié)構(gòu)化數(shù)據(jù)呢,正如字面上表達(dá)的,就是帶有一定結(jié)構(gòu)的數(shù)據(jù)。比如電話簿上有很多記錄數(shù)據(jù),每條記錄包含姓名、ID、郵件、電話等,這種結(jié)構(gòu)重復(fù)出現(xiàn)。
同類(lèi)
XML、JSON 也可以用來(lái)存儲(chǔ)此類(lèi)結(jié)構(gòu)化數(shù)據(jù),但是使用ProtoBuf表示的數(shù)據(jù)能更加高效,并且將數(shù)據(jù)壓縮得更小。
原理
ProtoBuf 是通過(guò)ProtoBuf編譯器將與編程語(yǔ)言無(wú)關(guān)的特有的 .proto 后綴的數(shù)據(jù)結(jié)構(gòu)文件編譯成各個(gè)編程語(yǔ)言(Java,C/C++,Python)專(zhuān)用的類(lèi)文件,然后通過(guò)Google提供的各個(gè)編程語(yǔ)言的支持庫(kù)lib即可調(diào)用API。(關(guān)于proto結(jié)構(gòu)體怎么編寫(xiě),可自行查閱文檔)
ProtoBuf編譯器安裝
Mac : brew install protobuf
舉個(gè)例子
1.先創(chuàng)建一個(gè)proto文件
message.proto
syntax?=?"proto3"; ? message?Person?{ ????int32?id?=?1; ????string?name?=?2; ???? ????repeated?Phone?phone?=?4; ???? ????enum?PhoneType?{ ????????MOBILE?=?0; ????????HOME?=?1; ????????WORK?=?2; ????} ? ????message?Phone?{ ????????string?number?=?1; ????????PhoneType?type?=?2; ????} }
?
?
2. 創(chuàng)建一個(gè)Java項(xiàng)目
并且將proto文件放置 src/main/proto 文件夾下
3. 編譯proto文件至Java版本
用命令行 cd 到 src/main 目錄下
終端執(zhí)行命令 : protoc --java_out=./java ./proto/*.proto
會(huì)發(fā)現(xiàn),在你的src/main/java 里已經(jīng)生成里對(duì)應(yīng)的Java類(lèi)
4. 依賴(lài)Java版本的ProtoBuf支持庫(kù)
這里只舉一個(gè)用Gradle使用依賴(lài)的栗子
implementation?'com.google.protobuf3.9.1'
5. 將Java對(duì)象轉(zhuǎn)為ProtoBuf數(shù)據(jù)
Message.Person.Phone.Builder?phoneBuilder?=?Message.Person.Phone.newBuilder(); Message.Person.Phone?phone1?=?phoneBuilder ????????.setNumber("100860") ????????.setType(Message.Person.PhoneType.HOME) ????????.build(); Message.Person.Phone?phone2?=?phoneBuilder ????????.setNumber("100100") ????????.setType(Message.Person.PhoneType.MOBILE) ????????.build(); Message.Person.Builder?personBuilder?=?Message.Person.newBuilder(); personBuilder.setId(1994); personBuilder.setName("XIAOLEI"); personBuilder.addPhone(phone1); personBuilder.addPhone(phone2); Message.Person?person?=?personBuilder.build(); long?old?=?System.currentTimeMillis(); byte[]?buff?=?person.toByteArray(); System.out.println("ProtoBuf 編碼耗時(shí):"?+?(System.currentTimeMillis()?-?old)); System.out.println(Arrays.toString(buff)); System.out.println("ProtoBuf?數(shù)據(jù)長(zhǎng)度:"?+?buff.length); 6.?將ProtoBuf數(shù)據(jù),轉(zhuǎn)換回Java對(duì)象 System.out.println("-開(kāi)始解碼-"); old?=?System.currentTimeMillis(); Message.Person?personOut?=?Message.Person.parseFrom(buff); System.out.println("ProtoBuf 解碼耗時(shí):"?+?(System.currentTimeMillis()?-?old)); System.out.printf("Id:%d,?Name:%s ",?personOut.getId(),?personOut.getName()); List?phoneList?=?personOut.getPhoneList(); for?(Message.Person.Phone?phone?:?phoneList) { ????System.out.printf("手機(jī)號(hào):%s?(%s) ",?phone.getNumber(),?phone.getType()); }
6. 將ProtoBuf數(shù)據(jù),轉(zhuǎn)換回Java對(duì)象
System.out.println("-開(kāi)始解碼-"); old?=?System.currentTimeMillis(); Message.Person?personOut?=?Message.Person.parseFrom(buff); System.out.println("ProtoBuf 解碼耗時(shí):"?+?(System.currentTimeMillis()?-?old)); System.out.printf("Id:%d,?Name:%s ",?personOut.getId(),?personOut.getName()); List?phoneList?=?personOut.getPhoneList(); for?(Message.Person.Phone?phone?:?phoneList) { ????System.out.printf("手機(jī)號(hào):%s?(%s) ",?phone.getNumber(),?phone.getType()); }
比較
為了能體現(xiàn)ProtoBuf的優(yōu)勢(shì),我寫(xiě)了同樣結(jié)構(gòu)體的Java類(lèi),并且將Java對(duì)象轉(zhuǎn)換成JSON數(shù)據(jù),來(lái)與ProtoBuf進(jìn)行比較。JSON編譯庫(kù)使用Google提供的GSON庫(kù),JSON的部分代碼就不貼出來(lái)了,直接展示結(jié)果
比較結(jié)果結(jié)果
運(yùn)行 1 次
【?JSON?開(kāi)始編碼?】 JSON 編碼1次,耗時(shí):22ms JSON?數(shù)據(jù)長(zhǎng)度:106 -開(kāi)始解碼- JSON 解碼1次,耗時(shí):1ms 【?ProtoBuf?開(kāi)始編碼?】 ProtoBuf 編碼1次,耗時(shí):32ms ProtoBuf?數(shù)據(jù)長(zhǎng)度:34 -開(kāi)始解碼- ProtoBuf 解碼1次,耗時(shí):3ms
運(yùn)行 10 次
【?JSON?開(kāi)始編碼?】 JSON 編碼10次,耗時(shí):22ms JSON?數(shù)據(jù)長(zhǎng)度:106 -開(kāi)始解碼- JSON 解碼10次,耗時(shí):4ms 【?ProtoBuf?開(kāi)始編碼?】 ProtoBuf 編碼10次,耗時(shí):29ms ProtoBuf?數(shù)據(jù)長(zhǎng)度:34 -開(kāi)始解碼- ProtoBuf 解碼10次,耗時(shí):3ms
運(yùn)行 100 次
【?JSON?開(kāi)始編碼?】 JSON 編碼100次,耗時(shí):32ms JSON?數(shù)據(jù)長(zhǎng)度:106 -開(kāi)始解碼- JSON 解碼100次,耗時(shí):8ms 【?ProtoBuf?開(kāi)始編碼?】 ProtoBuf 編碼100次,耗時(shí):31ms ProtoBuf?數(shù)據(jù)長(zhǎng)度:34 -開(kāi)始解碼- ProtoBuf 解碼100次,耗時(shí):4ms
運(yùn)行 1000 次
【?JSON?開(kāi)始編碼?】 JSON 編碼1000次,耗時(shí):39ms JSON?數(shù)據(jù)長(zhǎng)度:106 -開(kāi)始解碼- JSON 解碼1000次,耗時(shí):21ms 【?ProtoBuf?開(kāi)始編碼?】 ProtoBuf 編碼1000次,耗時(shí):37ms ProtoBuf?數(shù)據(jù)長(zhǎng)度:34 -開(kāi)始解碼- ProtoBuf 解碼1000次,耗時(shí):8ms
運(yùn)行 1萬(wàn) 次
【?JSON?開(kāi)始編碼?】 JSON 編碼10000次,耗時(shí):126ms JSON?數(shù)據(jù)長(zhǎng)度:106 -開(kāi)始解碼- JSON 解碼10000次,耗時(shí):93ms 【?ProtoBuf?開(kāi)始編碼?】 ProtoBuf 編碼10000次,耗時(shí):49ms ProtoBuf?數(shù)據(jù)長(zhǎng)度:34 -開(kāi)始解碼- ProtoBuf 解碼10000次,耗時(shí):23ms
運(yùn)行 10萬(wàn) 次
【?JSON?開(kāi)始編碼?】 JSON 編碼100000次,耗時(shí):248ms JSON?數(shù)據(jù)長(zhǎng)度:106 -開(kāi)始解碼- JSON 解碼100000次,耗時(shí):180ms 【?ProtoBuf?開(kāi)始編碼?】 ProtoBuf 編碼100000次,耗時(shí):51ms ProtoBuf?數(shù)據(jù)長(zhǎng)度:34 -開(kāi)始解碼- ProtoBuf 解碼100000次,耗時(shí):58ms
總結(jié)
編解碼性能
上述栗子只是簡(jiǎn)單的采樣,實(shí)際上據(jù)我的實(shí)驗(yàn)發(fā)現(xiàn)
次數(shù)在1千以下,ProtoBuf 的編碼與解碼性能,都與JSON不相上下,甚至還有比JSON差的趨勢(shì)。
次數(shù)在2千以上,ProtoBuf的編碼解碼性能,都比JSON高出很多。
次數(shù)在10萬(wàn)以上,ProtoBuf的編解碼性能就很明顯了,遠(yuǎn)遠(yuǎn)高出JSON的性能。
內(nèi)存占用
ProtoBuf的內(nèi)存34,而JSON到達(dá)106 ,ProtoBuf的內(nèi)存占用只有JSON的1/3.
結(jié)尾
其實(shí)這次實(shí)驗(yàn)有很多可待優(yōu)化的地方,就算是這種粗略的測(cè)試,也能看出來(lái)ProtoBuf的優(yōu)勢(shì)。
兼容
新增字段
在proto文件中新增 nickname 字段
生成Java文件
用老proto字節(jié)數(shù)組數(shù)據(jù),轉(zhuǎn)換成對(duì)象
Id:1994,?Name:XIAOLEI 手機(jī)號(hào):100860?(HOME) 手機(jī)號(hào):100100?(MOBILE) getNickname=
結(jié)果,是可以轉(zhuǎn)換成功。
刪除字段
在proto文件中刪除 name 字段
生成Java文件
用老proto字節(jié)數(shù)組數(shù)據(jù),轉(zhuǎn)換成對(duì)象
Id:1994,?Name:null 手機(jī)號(hào):100860?(HOME) 手機(jī)號(hào):100100?(MOBILE)
結(jié)果,是可以轉(zhuǎn)換成功。
編輯:黃飛
?
評(píng)論
查看更多