ProtocolBuffer 结合 LZO在 Hadoop中的使用(三)
1.自动生成Protocol buffer对象
1、把protoc.exe文件复制到 c:\windows 目录中(呵呵,这样最省事),window7放在windows/system32下
2、使用 protocol buffer 编译器编译 logFormat.proto 文件。打开命令行窗口,并定位到logFormat.proto文件目录中,运行命令 protoc --java_out=.
logFormat.proto(注意.后面有空格,否则会有错误),执行这个命令后会在当前目录下生成一个java类com.searchlog.LogFormat.java。
//http://code.google.com/apis/protocolbuffers/docs/proto.html option java_package = "com.searchlog"; message URI { optional string protocol=1; optional string host=2; optional int32 port=3; optional string path=4; optional string query=5; } message SearchLog { optional int64 id=39; optional string remote_addr=1; optional int64 time=2; //日志的时间,从1970年以来的毫秒值 optional int32 status=3; optional int32 body_bytes_sent=4; optional string ua=5; optional string CookieId=6; //si. 标识唯一用户. optional string lastVisitTime=7; //lt. 上次页面加载时间 optional string screenResolution=8; //sr. 屏幕分辨率 optional string language=33; //ln.浏览器语言 optional bool cookieEnabled=9; //ca. 是否启用cookie optional bool javaEnable=10; //ja. 是否启用java optional URI refer=11; //re. referer optional URI location=12; //lo. 当前页面的location optional string flashVersion=13; //fv.flashversion optional string clickPoint=14;//cp. 页面上点击坐标 optional int32 rand=15; //rand. 随机字符串 optional string keyword=16; //k. 搜索keyword optional int32 type=17; optional int32 pos=18; optional string pageStay=42; //ps.页面停留时间 optional string jsVersion=20; //v. 统计代码版本 optional int32 sendCount=21;//c. 本页发送统计数据的次数 optional string uuid=22; //uuid. 页面唯一id表示 optional string timestamp=23; //ts optional int32 ds=24; //ds. 页面直达区个数 optional int32 vs=25;//vs. 页面普通结果数 optional string pid=26; //pid. 点击链接的节目id optional string vid=27; //vid. 点击链接的视频id optional int32 pageno=28; //pn. 当前页码 optional int32 mc=29; //mc. 点击直达区更多,1点击展开,2点击隐藏 optional int32 ct=30; //ct. 直达区分类: optional int32 directpos=31; //directpos optional int32 origin=32; //0,tudou. 1,youku optional string ok=34; //ok,用户在输入框中输入的词 optional string os=35; //os,用户通过kubox选择的词 optional int32 ki=37; //ki.用户选择的第几个提示词 optional string lastKeyword=38; //lk.上一次的搜索词 optional string kt=36; //kt. optional int32 sh=40; //sh. 是否屏蔽词。1,是。0或者不填,不是。 optional int32 site=41; };
这边介绍一下该文件定义的语法:
package com.searchlog.proto; // 声明包,表明此声明下方的数据结构都属于此包, package起到命名空间的作用避免变量命名冲突,使用protoc.exe编译.proto文件后,package名即转换为相应的命名空间;
定义在message A里边的message B在protoc.exe编译后,会成为package 下的两个平行的类:
public static final class URI extends com.google.protobuf.GeneratedMessage implements URIOrBuilder { ... }
public static final class SearchLog extends com.google.protobuf.GeneratedMessage implements SearchLogOrBuilder{ ... }
message中的每一个成员都必须使用域规则(field rules)required、optional、repeated 三者之一作为前置声明。
required 修饰的成员必须被赋值,如果libprotobuf 在debug模式下编译,那么对包含未初始化的repeated成员的对象进行序列化的时候将会失败,如果libprotobuf经过优化编译,那么序列化会继续进行而不产生错误,但对序列化后的数据进行解析会失败。
对于optional 成员,其值可以不被初始化,但在可行的时候应为optional 成员设定默认值。如果没有默认值,那么系统会自动为optional成员添加默认值,对于数值类型,默认值是0;对于字符串类型,默认为空串;bool类型默认值为false。对于嵌套的message,其默认值为默认的对象或原型,即未初始化的对象。访问默认对象的成员得到的将是系统默认值。
repeated成员意味着可以包含多个此类型的成员(也可以是0个)。
对于message中的required 和optional 变量var,使用相应的set_var函数对成员变量赋值;对于repeated 变量 revar, 使用 add_revar 函数进行赋值。
拿其中的一个字段来说:
生成的代码中含有:
// optional string lastVisitTime = 7; private java.lang.Object lastVisitTime_ = ""; public boolean hasLastVisitTime() { return ((bitField0_ & 0x00000080) == 0x00000080); } public String getLastVisitTime() { java.lang.Object ref = lastVisitTime_; if (!(ref instanceof String)) { String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); lastVisitTime_ = s; return s; } else { return (String) ref; } } public Builder setLastVisitTime(String value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000080; lastVisitTime_ = value; onChanged(); return this; } public Builder clearLastVisitTime() { bitField0_ = (bitField0_ & ~0x00000080); lastVisitTime_ = getDefaultInstance().getLastVisitTime(); onChanged(); return this; } void setLastVisitTime(com.google.protobuf.ByteString value) { bitField0_ |= 0x00000080; lastVisitTime_ = value; onChanged(); }
使用的时候:
SearchLog.Builder builder = SearchLog.newBuilder() builder.setLastVisitTime(value.get(0)); SearchLog msg = builder.build();
对于任意类型的成员var, 都可以通过 have_var() 来判断成员变量是否已经赋值;通过clear_var() 来清除变量内容;对于对象,可以通过 IsInitialized()函数判断required类型成员是否全部初始化;使用clear()清理对象的所有成员。对于repeated成员b,b_size()函数可以用来获取b成员的个数。
对于string成员S,使用mutable_S()函数将使得你得到直接指向该成员的指针(direct pointer),而无论该成员是否已经初始化。
message 成员的类型可以是基础类型bool
, int32
, float
, double
与string
;
也可以是复杂的自定义类型,自定义类型可以嵌套。更详细的类型支持列表:http://code.google.com/apis/protocolbuffers/docs/proto.html
3.将生成的ProtoBufferPractice.java文件引入eclipse
4.把下载的protobuf-java-2.4.1.jar也引入工程
option java_package = “com.example.foo”;
我认为比较重要的文件级选项有
java_package ——指定生成的java代码的packge。
java_outer_classname ——protoc会为每个.proto文件一个java类,其中每个message都做作为该java类的内部类,这个选项就用于指定外围类的类名。
optimize_for ——用于优化protoc产生的代码,有三种模式:
SPEED –产生的代码将具有最好的序列化/反序列化执行速度。
CODE_SIZE –产生的代码将具有更少的代码行数。
LITE_RUNTIME –protocol buffer有一个简化版的runtime库,比如要生成用于手机平台的代码,可以使用这个选项。
另外,packed是一个字段级选项,在上面字段修改符repeated中提到了。
分析:
个人认为pb之所以快和省流量除了本省字节少以外,序列化过程减少的字节数是主要因素
具体可参考http://kangsg219.iteye.com/blog/904762
其他参考:http://www.searchtb.com/2010/11/protocol-buffers%E7%9A%84%E5%BA%94%E7%94%A8%E4%B8%8E%E5%88%86%E6%9E%90.html
在实践中具体选择什么还需要依情况而定:
http://www.oschina.net/question/12_10307