什么是ProtoBuf
一种数据存储、传输格式,用于结构化数据,主要的优点有结构紧凑、占用空间小、结构化速度快、扩展性良好,同时平台、语言无关。
使用步骤
1. 定义proto文件(结构描述)
2. 生成对应语言的实现(JAVA等)
3. 生成数据端将数据结构化为Proto格式数据流
4. 接收端解析数据
例:http://blog.csdn.net/wanyanxgf/article/details/8817925
proto文件定义
option java_package:生成Java文件的package
option java_outer_classname:生成的Java文件名
message:定义一个message,相当于java的class
enum:定义一个枚举,相当于Java的枚举
required:表示此字段必须存在,即在结构化的流当中必须存在此字段,否则不能反解析
optional:此字段表示可选,可以有也可以没有
repeated:代表此字段可以出现0~n次,可以认为是一个Java中的list
default:代表默认值,此值不在流中体现,即接收端解析后的默认值是和接收端的类中定义的是一样的,和发送端的无关
字段后面的数字:字段的数字表示,在流中表示字段,定义之后不能改变(因为存储方式关系,尽量使用15因为的)
常用字段类型(和Java中对应):
double==>double
float==>float
bool==>bool
string==>String
bytes==>byte[]
int32/uint32/sint32==>int(uint32为无符号,因为int32的存储方式导致对负数的处理较为耗资源,所以引入sint32,对负数处理较好)
int64/uint64/sint64==>long(同上)
fixed*==>int,long(用固定长度来存储,在数字较大的时候使用动态方式处理较耗资源使用比较好)
option java_package = "com.meituan.service.mobile.protobuf.hotel";
option java_outer_classname = "HotelCommentProto";
message HotelCommentList{
repeated HotelComment comments=1;
}
message HotelComment{
required int64 id=1;
optional int64 poi_id=2;
optional int64 did=3;
optional int64 user_id=4;
optional string user_name=5;
optional int32 score=6 [default=5];
optional string score_text=7;
optional string comment=8;
optional int64 feedback_time=9;
optional HotelType ht=10;
}
enum HotelType {
NORMAL = 0;
EXP = 1;
}
Encoding
先解释下varints,是一种用一个到多个byte表达
integers的方式
1. 将一个数字转成2进制,从低位起每7个bit一组,如:150=000001 0010110
2. 从低到高取bit组,如果后面还有bit组则在第一位补充1,如果没有则补充0。【1】000001 【0】0010110
示例一
//定义
required int32 a = 1;
//赋值
a=150;
encode结果:
08 96 01
二进制为:
00001000 10010110 0000001
分析:
根据varints规则,上述结果为2组byte
byte组1:00001000
代表字段的定义,规则为:(field_number << 3) | wire_type
由此规则判断:第一个bit【0】代表没有后续byte组,后三个byte【000】代表wire type为0(wire type下面再解释),中间几位代表字段的数字表示【0001】,为1
注:这个描述可以解释上面说的字段的数字描述尽量使用15以内的,如果超过15,则字段的定义无法用一个byte表示,需要多个byte。
byte组2:10010110 0000001
根据规则两个byte的第一个bit位【1】、【0】分别代表后面还有和后面没有了
因为数字生成时是低位在前,所以还原需要把byte反排下,【000001】【0010110】,10010110=128+16+4+2=150
不同的wire type代表不同的结构化方式:
type=0:即采用上述描述的varints规则
type=1,5:已经明确指定数据长度,存储采用低位在前的方式
type=2:以String举例
//定义
required string b = 2;
//赋值
b=testing
结果为:
12 07 74 65 73 74 69 6e 67
byte组1:12=00010010
表示type=2,字段编号=2
byte组2:07=00000111
表示后面有7个byte是这个字段的value
byte组3:74 65 73 74 69 6e 67
value utf-8编码后的值
repeated的存储
optional int64 user_id=4;
如果此字段有多个值,最后的流中会出现多个 4:value1,4:value2的结果对(不一定是挨在一起的),在解析时在组合到一起
optional int64 user_id=4[packed=true];
存储方式类似string,如:22 06 02 8e 02,其中【22】为字段定义,【06】为后续数据长度,后面为数据。
变更的影响
1. 所有字段的数字表示不可变更
2. 新增的字段是required,如果生产方和解析方版本不一致,则无法解析
3. 新增的字段是optional或者repeated。如生产方使用新版本,旧版本的解析方抛弃新增字段的值。如生产方使用旧版本,新版本的解析方无相关数据填充
4. 非required字段删除,影响同3(不建议)
5. 修改字段类型,int32, uint32, int64, uint64, bool可以相互解析,不过可能会发生数据截断
6. 修改字段类型,string、bytes之间可以互相解析(bytes都为utf-8可解析)
7. 修改字段修饰符,生产方的该字段为repeated,而解析方为optional,可以解析。如果是基本类型或者是string,则解析时使用最后一个值(每次都是后一个覆盖前一个),如果是其他类型(message等),会执行merge,即多个message属性合并,都存在的以后一个为准。
Java代码生成规则
- 对每一个message生成一个*OrBuider的接口和一个message的
- *OrBuider接口中主要是对message属性的获取方法
- message的类实现*OrBuilder接口同时继承GeneratedMessage,message类不能直接修改。必须使用其内部的builder构建。
- 内部builder中包含message类的写方法