1.protocol buffer需要定义.proto描述文件,然后通过google提供的编译器生成特定的模型文件,之后就可以作为正常的java对象使用了
2.不可以直接创建对象,需要通过Builder进行
3.只有Builder才可以进行set
4.可以通过对象的toByteArray()和parseFrom()方法进行编码和解码
5.模型文件很大(至少在java这里是如此),其中所有的代码都是定制的,这其实是它很大的缺点之一
接着我们将继续深入探究protobuf的编码原理。
主要分为两个部分
第一部分是之前留下的几个伏笔展示protobuf的使用特性
第二部分是分析protobuf的编码原理,解释特性背后的原因
第一部分,Protobuf使用特性
1.不同类型对象的转换
我们先定义如下一个.proto文件
syntax = “proto3”;
option java_package = “cn.tera.protobuf.model”;
option java_outer_classname = “DifferentModels”;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
message Article {
string title = 1;
int32 wordsCount = 2;
string author = 3;
}
其中我们定义了2个模型,一个Person,一个Article,虽然他们的字段名字不相同,但是类型和编号都是一致的
接着我们生成.java文件,最终文件结构如下图
此时我们尝试做如下的一个转换
/**
- 测试不同模型间的转换
- @throws Exception
*/
@Test
public void parseDifferentModelsTest() throws Exception {
//创建一个Person对象
DifferentModels.Person person = DifferentModels.Person.newBuilder()
.setName(“person name”)
.setId(1)
.setEmail(“tera@google.com”)
.build();
//对person编码
byte[] personBytes = person.toByteArray();
//将编码后的数据直接merge成Article对象
DifferentModels.Article article = DifferentModels.Article.parseFrom(personBytes);
System.out.println(“article’s title:” + article.getTitle());
System.out.println(“article’s wordsCount:” + article.getWordsCount());
System.out.println(“article’s author:” + article.getAuthor());
}
输出结果如下
article’s title:person name
article’s wordsCount:1
article’s author:tera@google.com
可以看到,虽然jsonBytes是由person对象编码得到的,但是可以用于article对象的解码,不但不会报错,所有的数据内容都是完整保留的
这种兼容性的前提是模型中所定义的字段类型和序号都是一一对应相同的
在平时的编码中,我们经常会遇到从数据库中读取数据模型,然后将其转换成业务模型,而很多时候,这2种模型的内容其实是完全一致的,此时我们也许就可以使用protobuf的这种特性,就可以省去很多低效的赋值代码
2.protobuf序号的重要性
我们看到在定义.proto文件时,字段后面会跟着一个"= X",这里并不是指这个字段的值,而是表示这个字段的“序号”,和正确地编码与解码息息相关,在我看来是protocol buffer的灵魂
我们定义如下的.proto文件,这里注意,Model1和Model2的name和id的序号有不同
syntax = “proto3”;
option java_package = “cn.tera.protobuf.model”;
option java_outer_classname = “TagImportance”;
message Model1 {
string name = 1;
int32 id = 2;
string email = 3;
}
message Model2 {
string name = 2;
int32 id = 1;
string email = 3;
}
定义如下的测试方法
/**
- 序号的重要性测试
- @throws Exception
*/
@Test
public void tagImportanceTest() throws Exception {
TagImportance.Model1 model1 = TagImportance.Model1.newBuilder()
.setEmail(“model1@google.com”)
.setId(1)
.setName(“model1”)
.build();
TagImportance.Model2 model2 = TagImportance.Model2.parseFrom(model1.toByteArray());
System.out.println(“model2 email:” + model2.getEmail());
System.out.println(“model2 id:” + model2.getId());
System.out.println(“model2 name:” + model2.getName());
System.out.println(“-------model2 数据---------”);
System.out.println(model2);
}
输出结果如下
model2 email:model1@google.com
model2 id:0
model2 name:
-------model2 数据---------
email: “model1@google.com”
1: “model1”
2: 1
可以看到,虽然Model1和Model2定义的字段类型和名字都是相同的,然而name和id的序号颠倒了一下,导致最终model2在解析byte数组时,无法正确将数据解析到对应的字段上,所以输出的id为0,而name字段为null
不过即使字段无法一一对应,但在输出model2.toString()时,我们依然可以看到数据是被解析到了,只不过无法对应到具体字段,只能用1,2来表示其字段名
3.protobuf序号对编码结果大小的影响
protobuf的序号不仅影响编码、解码的正确性,一定程度上还会影响编码结果的字节数
我们在上面的.proto文件中增加一个Model3,其中Model3中定义的字段没有变化,但是序号更改为16,17,18
syntax = “proto3”;
option java_package = “cn.tera.protobuf.model”;
option java_outer_classname = “TagImportance”;
message Model1 {
string name = 1;
int32 id = 2;
string email = 3;
}
message Model2 {
string name = 2;
int32 id = 1;
string email = 3;
}
message