大家好,俺又來寫博客了.......上次劇情預告說,這次會寫hive的博客.......好吧,那俺就不打算寫hive了.......老碼農路子就是要野(本人不老,不能說得影響了找女票)......這次咱們玩什么呢,我之前就看重了一個比較好玩的小玩意兒,那就是來自google的技術,protobuf.
上次的博客我看了之后很失望啊,閱讀數並不高....我在想是不是大家對hadoop之類的並不感興趣,所以就先換換口味吧.Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言數據標准,目前已經正在使用的有超過 48,162 種報文格式定義和超過 12,183 個 .proto 文件。他們用於 RPC 系統和持續數據存儲系統。Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化。它很適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平台無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python 三種語言的 API。
ok,這就是protubuf的一些比較抽象的介紹,不過簡單地說,protobuf其實就是一種數據傳輸的格式,事實上,我們需要知道pb這種格式用來傳輸數據到底會給我們帶來如何的好處.我們可以拿pb格式和json做對比.其實json這種數據傳輸的格式大家應該非常熟悉.但是json有什么不好的呢,首先json大,json格式是字符串形式,和壓縮為二進制的pb格式肯定是要來的大的.此外,json很坑,因為需要對不同的語言編寫json解析的程序.......php和python自然可以比較輕松的解析,java就坑了.......而且這些解析器的性能並一致,有好有壞.pb因為是大廠的技術嘛,人家搞一個編譯器,編譯為c++,java,python的解析代碼,不就搞定了.......好吧,大廠技術就是雄厚.......我等也只能仰望.....(老子要是有時間,也去寫一個)....
廢話不多說,直接搞起來.首先,你要去下載pb的tar包.至於安裝包在哪里,少年郎,去google上找吧........我裝的是2.6.1版,安裝非常簡單.
1 tar zxvf protobuf-2.6.1.tar.gz 2 cd protobuf-2.6.1 3 ./configure 4 make 5 make check 6 make install
這個編譯的過程依舊是無比的漫長.......很懷念大一的時候,寫個hello world,一秒不到就編譯完成,然后很快就能輸出結果的那種快樂的時光.........人的一生就是在等待中度過,等待一個可以白頭到老的人,等待一份可以吃飽飯的工作,等待有一天,能夠成為想成為的那個人.......其實等待是因為有希望,沒有希望,等待似乎就沒有意義了......所以,我一直在想,女票是會有的,飯也可以吃飽的,人不能沒有希望........
ok,我們安裝完了,來看看有沒有安裝上吧......
1 protoc --version
返回:libprotoc 2.6.1. 這樣,就安裝成功了.
然后我們怎么用這個工具呢?
- 首先,我們要做的是,定義一套.proto格式的消息的定義文件.我們在具體的使用中來慢慢來港如何書寫這個proto綴的文件.
1 message student{ 2 required int64 sid = 1;//學生id 3 required string name = 2;//學生姓名 4 }
這里面我們發現:
- message類似於C里面的struct,表示我們定義的是一個消息類型.
- studeng是具體的類型名稱.
- required是表示這個字段是必須賦值的.
- int64和string是該字段的類型.
- =后面的數字表示是該字段在二進制文件中的序號,name為2,表示它一定在sid的后面.
現在我們如果想要加上一個性別的字段和年齡的字段,性別的字段是enum類型.年齡類型是int32,怎喵加呢?很簡單.
1 enum Sex{ 2 MALE = 1; 3 FEMALE = 2; 4 } 5 6 message Student{ 7 required int64 sid = 1;//學生id 8 required string name = 2;//學生姓名 9 optional Sex sex = 3; //學生性別 10 optional int32 age = 4;//學生年紀 11 }
easy啊.等等,現在我們又有一個消息,比如我們有個班級的消息,希望能夠列出里面每個學生的這些基本信息,怎么搞呢?
首先,我們需要定義一個班級的消息.
1 message Banji{ 2 required int64 cid = 1;//班級的id 3 required string cname = 2;//班級的編號 4 repeated Student students = 3;//學生 5 }
這里我們要注意幾個點:
- required前綴表示該字段為必要字段,既在序列化和反序列化之前該字段必須已經被賦值。與此同時,在Protocol Buffer中還存在另外兩個類似的關鍵字,optional和repeated,帶有這兩種限定符的消息字段則沒有required字段這樣的限制。
- 標簽值為1到15的字段在編碼時可以得到優化,既標簽值和類型信息僅占有一個byte.標簽范圍是16到2047的將占有兩個bytes,而ProtocolBuffer可以支持的字段數量則為2的29次方減一。有鑒於此,我們在設計消息結構時,可以盡可能考慮讓repeated類型的字段標簽位於1到15之間,這樣便可以有效的節省編碼后的字節數量。
- 在每個消息中必須至少留有一個required類型的字段.
- 如果打算在原有消息協議中添加新的字段,同時還要保證老版本的程序能夠正常讀取或寫入,那么對於新添加的字段必須是optional或repeated。道理非常簡單,老版本程序無法讀取或寫入新增的required限定符的字段。
- 在原有的消息中,不能移除已經存在的required字段,optional和repeated類型的字段可以被移除,但是他們之前使用的標簽號必須被保留,不能被新的字段重用。
- int32、uint32、int64、uint64和bool等類型之間是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之間是兼容的,這意味着如果想修改原有字段的類型時,為了保證兼容性,只能將其修改為與其原有類型兼容的類型,否則就將打破新老消息格式的兼容性。
這里還有一個問題,Banji這個消息要用到Student的定義,我們可以回憶一下java中的處理方式,這種互相定義之間的依賴可以用package和import來解決.這樣,我們可以給出完整的.proto的定義方式:
test.student.proto
package test; option java_package = "com.songfy.pb"; option java_outer_classname = "StudentProtobuf"; enum Sex{ MALE = 1; FEMALE = 2; } message Student{ required int64 sid = 1;//學生id required string name = 2;//學生姓名 optional Sex sex = 3; //學生性別 optional int32 age = 4;//學生年紀 }
test.banji.proto
1 import "test.student.proto"; 2 package test; 3 option java_package = "com.songfy.pb"; 4 option java_outer_classname = "BanjiProtobuf"; 5 message Banji{ 6 required int64 cid = 1;//班級的id 7 required string cname = 2;//班級的編號 8 repeated Student students = 3;//學生 9 }
ok,這樣我們就把我們的消息類型定義好了....那么如何寫代碼呢?下面我們細細道來.
2.編譯.proto
我們這里用java來做示例,C++也成,但是還是java稍微方便一下,大家可以用C++試一試,python呢就算了,俺不太會python,也沒有興趣去學,個人喜好,勿噴,嗯,就這樣.ok,那現在我們就來編譯這個玩意兒.
1 protoc --java_out="/usr/home/feng/protos/src" test.banji.proto test.student.proto
吊炸天,瞬間產生了1000多行代碼,來自google的技術,四國一!
不過現在問題來了,這代碼怎么用?這代碼看都看不懂,怎么用呢?不要驚慌,一定不要驚慌.......先靠到你的elipse再說....然后你就會發現一大堆的錯誤......嗯........
這個解決的方法很簡單,將protobuf-java-2.6.1.jar下載下來,加入到我們的path中,那我們的問題就都解決了.
接下來,我們來看看如何寫代碼.嗯,終於到了寫代碼的時候了........
1 package com.songfy.pb; 2 3 import java.io.FileInputStream; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.util.ArrayList; 7 import java.util.List; 8 9 import com.songfy.pb.BanjiProtobuf.Banji; 10 import com.songfy.pb.StudentProtobuf.Sex; 11 import com.songfy.pb.StudentProtobuf.Student; 12 13 public class Main { 14 15 public static void main(String[] args) throws IOException { 16 17 //獲取student的build sb 18 Student.Builder sb = Student.newBuilder(); 19 //加入兩個學生 20 sb.setSid(110); 21 sb.setName("shuaiguo"); 22 sb.setSex(Sex.MALE); 23 sb.setAge(11); 24 25 List<Student> list = new ArrayList<Student>(); 26 list.add(sb.build()); 27 28 sb = Student.newBuilder(); 29 sb.setSid(119); 30 sb.setName("lilei"); 31 sb.setSex(Sex.FEMALE); 32 sb.setAge(20); 33 34 list.add(sb.build()); 35 36 //產生一個班級 37 Banji.Builder bb = Banji.newBuilder(); 38 bb.setCid(13); 39 bb.setCname("dafengqi"); 40 bb.addAllStudents(list); 41 42 //刷入到文件中 43 FileOutputStream fos = new FileOutputStream("C:/Users/songfy/Desktop/test.protoout"); 44 bb.build().writeTo(fos); 45 fos.close(); 46 47 //從文件讀回 48 Banji bb1 = Banji.parseFrom(new FileInputStream("C:/Users/songfy/Desktop/test.protoout")); 49 System.out.println(bb1.getCid() + "\t" + bb1.getCname()); 50 for(Student s: bb1.getStudentsList()){ 51 System.out.println(s.getSid() + "\t" + s.getName() + "\t" + s.getSex() + "\t" + s.getAge()); 52 } 53 54 } 55 56 }
這就是我們代碼了,似乎非常easy啊........
這樣,我們可以看到輸出結果為:
13 dafengqi
110 shuaiguo MALE 11
119 lilei FEMALE 20
這正是我們想要的,這次的博客時間就到此為止了,我們下次再見........2333333......2333333.......