protobuf java基础
1:定义proto文件:
以一个地址薄为例,从建立一个.proto文件开始,为需要序列化的数据接口加入一个message属性,在message里面,为每一个字段指定名称和类型(算是IDL吧),如下所示:
package demo;
option java_package = "com.sunchao.serializer.testSerializer.protobuf";
option java_outer_classname = "PersonProto";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 3;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
} repeated PhoneNumber phone = 4; message CountryInfo {
required string name = 1;
required string code = 2;
optional int32 number = 3;
}
} message AddressBook {
repeated Person person = 1;
}
下面我们来看看每个部分的意义:
为了避免命名冲突,.proto文件以包声明开始(proto文件命令冲突),在java中特别指定一个java_package属性,为Java的包。正像上面的例子,虽然提供了java_package属性,你通常还是应该定义package属性以避免在ProtocolBuffers中命名冲突。包声明以后,有两个Java属性:java_package和java_outer_classname。java_package表示生成的Java代码的包,如果没有指定,编译器会根据package属性确定包名。java_outer_classname属性定义生成文件的类名。如果没有指定,会根据文件名进行 转换,如:"my_proto.proto"缺省会使用MyProto作为外部类名。
接下来是定义message属性,一个message是包含了各种类型字段的聚集。有很多标准的变量类型可以使用,包 括:bool,int32,float,double和string。你也可以使用其他的message作为字段类型。正像例子中的Person包含了 PhoneNumber,而AddressBook包含了Persion。甚至可以在message内部定义message,例 如:PhoneNumber就是在Persion里面定义的。你还可以定义enum类型,正像指定电话号码类型的MOBILE、HOME、WORK。
其中“=1”,“=2”表示每个元素的标识号,它会用在二进制编码中对域的标识。标识号1-15由于使用时会比那些高的标识号少一个字节,从最优化角度考虑,可以将其使用在一些较常用的或repeated元素上,对于16以上的则使用在不常用的或optional的元素上。对于repeated的每 个元素都需要重复编码该标识号,所以repeated的域进行优化来说是最显示的。
每个字段必须提供一个修饰词:
Ø required:表示字段必须提供,不能为空。否则message会被认为是未初始化的,试图build未初始化的message会抛出 RuntimeException。解析未初始化的message会抛出IOException。除此之外,一个required字段与optional 字段完全相同。
Ø optional:可选字段,可以设置也可以不设置。如果没有设置,会设置一个缺省值。可以指定一个缺省值,正像电话号码的type字段。否则,使用系统 的缺省值:数字类型缺省为0;字符类型缺省为空串;逻辑类型缺省为false;对于嵌入的message,缺省值通常是message的实例或原型。
Ø repeated:字段可以被重复(包括0),可等同于动态数组或列表。其中存储的值列表的顺序是被保留的。
Required修饰的字段是永久性的,在使用该修饰符时一定要特别小心。如果在以后想要修改required域为optional域时会出现问题。对于访问旧接口的用户来说没有该字段时,将会认为是不合法的访问,将会被拒绝或丢弃。其中google的一些工程师给出的建议是如果不是必须,就尽量少用required修饰符。
2:Protocol Buffer API使用
接下来具体看一下所生成的java代码及其中的方法。在AddressBookProtos.java中可以看出,其中的内部类对应的是addressbook.proto中定义的格式。每个类都有它自己的Builder类,通过它即可以创建该类的实例。
Messages和Builders都会为每个域创建自动的访问方法,其中messages只有getters,而builders有getters和setters。下面是Person类message的访问方法:
// required string name = 1;
boolean hasName();
String getName(); // required int32 id = 2;
boolean hasId();
int getId(); // optional string email = 3;
boolean hasEmail();
String getEmail(); // repeated .demo.Person.PhoneNumber phone = 4;
java.util.List<com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumber>
getPhoneList();
com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumber getPhone(int index);
int getPhoneCount();
java.util.List<? extends com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumberOrBuilder>
getPhoneOrBuilderList();
com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumberOrBuilder getPhoneOrBuilder(
int index);
Person类builder的访问方法(Person.Builder):
// required string name = 1;
public boolean hasName();
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName(); // required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId(); // optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail(); // repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);
public Builder setPhone(int index, PhoneNumber value);
public Builder addPhone(PhoneNumber value);
public Builder addAllPhone(Iterable<PhoneNumber> value);
public Builder clearPhone();
正如你所见,对于每个域都有简单的javabean风格的getters和setters。对于具有单一值的类型,有has方法用来表示该值是否有设置。当然也可以通过clear方法来将该字段的值清空。
重复域也有额外的方法,如count方法用来统计当前重复域的大小,getters和setters用于根据索引来获取或设置值。add方法用于将一个新元素添加到重复域中,addAll方法则将一组元素添加到重复域中。
上述示例中访问方法的名称采用了驼峰式命名,对应在.proto文件中采用的是小写字母+下划线的命名。这种转换是由protoc编译器自动完成的,我们只需要按照这种规约定义.proto文件即可。
3:枚举和内部类
public static PhoneType valueOf(int value) {
switch (value) {
case 0: return MOBILE;
case 1: return HOME;
case 3: return WORK;
default: return null;
}
}
PhoneNumber也是作为Person的一个内部类而产生的。
public static final class PhoneNumber extends
com.google.protobuf.GeneratedMessage
implements PhoneNumberOrBuilder
4:Builders 对Messages
由编译器自动生成的message类是不可变的(final),一旦一个message对象构建以后,就象java中的String类一样是不可变的。创建一个message时,必须首先创建一个builder,设置必须的一些值后,再调用builder的build()方法。
也许你已经注意到了,builder的每个方法在消息修改后又会返回builder,这个返回对象又可以调用其它方法。这种方式对于在同一行操作不同的方法提供了便利。如下的代码示例,创建一个Person实例。
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.addPhone(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
5:标准的Message方法
对于每个message或builder类也包含一些方法用于检查或操作整个消息,如:
isInitialized()
:检查是否所有的required字段已经设置了值;
toString()
:返回一个易于阅读的消息结果,对于调试来说非常有用;
mergeFrom(Message other)
: 将其它内部merger到当前的消息中,重写单一值域或者新增repeated域,仅用于builder。
clear()
:将所有域清空设置,仅用于builder。
6:解析及序列化
最终,protocol buffer类就可以通过一些方法来完成消息的读写入及读取。如:
byte[] toByteArray()
:
消息序列化并返回一个字节数组;
static Person parseFrom(byte[] data)
:
从一个特定的字节数组解析成消息;
void writeTo(OutputStream output)
:
序列化消息并将其写入到OutputStream中;
static Person parseFrom(InputStreaminput)
:
从InputStream流中读取并解析消息。
上述提供的仅仅是解析及序列化的一组接口,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/java/com/google/protobuf/Message.html中查阅更全面的的接口。
7:写入消息
接下来先看如何来用protocol buffer类,对于地址薄应用首先需要将个人资料写入地址薄中。为了做到这些,需要创建protocol buffer类并将信息写入。程序设计如下,会先从一个文件读取AddressBook信息,通过用户手工输入一个Person的信息,交将其回写至 AddressBook文件中。代码示例如下:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream; import com.sunchao.serializer.testSerializer.protobuf.PersonProto.AddressBook;
import com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person; public class TestAddress { static Person prompteForAddress(BufferedReader stdin,
PrintStream stdout) throws Exception {
Person.Builder person = Person.newBuilder();
stdout.print("Enter person ID: ");
person.setId(Integer.valueOf(stdin.readLine())); stdout.print("Enter name: ");
person.setName(stdin.readLine()); stdout.print("Enter email address(blank for none): ");
String email = stdin.readLine();
if (email != null && email.length() > 0)
{
person.setEmail(email);
} while (true)
{
stdout.print("Enter a phone number(or leave blank to finish): ");
String number = stdin.readLine();
if (number == null || number.length() == 0)
{
break;
} Person.PhoneNumber.Builder phoneNumber = Person.PhoneNumber.newBuilder();
phoneNumber.setNumber(number); stdout.print("Is this a mobile, home, or work phone?(please enter in home|work|mobile):");
String type = stdin.readLine();
if ("home".equalsIgnoreCase(type)) {
phoneNumber.setType(Person.PhoneType.HOME);
} else if ("work".equalsIgnoreCase(type)) {
phoneNumber.setType(Person.PhoneType.WORK);
} else if ("mobile".equalsIgnoreCase(type)) {
phoneNumber.setType(Person.PhoneType.MOBILE);
} else {
stdout.println("unknown phone type! using default.");
} person.addPhone(phoneNumber);
} return person.build();
} public static void main(String args[]) throws Exception {
if (args.length != 1)
{
System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE");
System.exit(-1);
} AddressBook.Builder book = AddressBook.newBuilder(); try {
book.mergeFrom(new FileInputStream(args[0]));
} catch (FileNotFoundException e) {
System.out.println(args[0] + ": File not found. Create a new file.");
} book.addPerson(prompteForAddress(new BufferedReader(new InputStreamReader(System.in)),
System.out));
FileOutputStream output = new FileOutputStream(args[0]);
try {
book.build().writeTo(output);
} finally {
output.close();
}
}
}
8:读取消息
import java.io.FileInputStream; import com.sunchao.serializer.testSerializer.protobuf.PersonProto.AddressBook;
import com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person;
import com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumber; public class ListPerson { static void print(AddressBook addressBook)
{
for (Person person : addressBook.getPersonList())
{
System.out.println("Person ID: " + person.getId());
System.out.println("Person Name: " + person.getName());
if (person.hasEmail())
{
System.out.println(person.getEmail());
} for (PhoneNumber number : person.getPhoneList())
{
switch (number.getType())
{
case HOME :
System.out.print(" Home Phone #: " + number.getNumber());
break; case MOBILE :
System.out.print(" Mobile Phone #: " + number.getNumber());
break; case WORK:
System.out.print(" Work Phone #: " + number.getNumber());
break; default :
System.out.print(" Unknown Phone type #: " + number.getNumber());
break;
}
}
System.out.println("********************************************");
}
} public static void main(String args[]) throws Exception
{
if (args.length != 1) {
System.err.println("Usage: ListPeople Address <file-option>:");
System.exit(-1);
} AddressBook addressBook = AddressBook.parseFrom(new FileInputStream(args[0])); print(addressBook);
}
}
9:对Protocol Buffer进行扩展
有时会发现在发布完protocolbuffer代码后,需要对其进行扩展升级。如果想让新代码向后兼容,而且老代码能够向前兼容,此时需要遵循以下的规则。
不能改变已存在域的标识号;
不要任意添加或删除required修饰的域;
可以删除optional或repeated修饰的域;
可以新增optional或repeated修饰的域,但是必须使用新的标识号。
如果按照上述规约进行了升级,旧的代码将可以读取新的消息并将一些新的字段忽略掉。对于旧代码,被删除的optional域将会使用其默认值,删除的repeated域将会被置空。新代码中也将能够透明地读取旧的消息,但是有一点需要明确,那就是新的optional域不能出现在旧消息中,可以通过 has方法进行明确检查,或者在.proto文件中为该字段提供一个默认值。如果一个optional元素没有明确的声明默认值的话,则会根据其类型取默 认值,如:字符串类型,取空串为默认值;布尔类型取false为其默认值;数字类型取0为其默认值。如果新增了一个repeated域,新代码将不能判断 其是否是空,老代码也不会设置其值,且它并没有has方法。
protobuf java基础的更多相关文章
- Java基础面试题(史上最全、持续更新、吐血推荐)
文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...
- Java基础知识(壹)
写在前面的话 这篇博客,是很早之前自己的学习Java基础知识的,所记录的内容,仅仅是当时学习的一个总结随笔.现在分享出来,希望能帮助大家,如有不足的,希望大家支出. 后续会继续分享基础知识手记.希望能 ...
- [Java面经]干货整理, Java面试题(覆盖Java基础,Java高级,JavaEE,数据库,设计模式等)
如若转载请注明出处: http://www.cnblogs.com/wang-meng/p/5898837.html 谢谢.上一篇发了一个找工作的面经, 找工作不宜, 希望这一篇的内容能够帮助到大 ...
- 【JAVA面试题系列一】面试题总汇--JAVA基础部分
JAVA基础 基础部分的顺序: 基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法 线程的语法,集合的语法,io 的语法,虚拟机方面的语法 每天几道,持续更新!! 1.一个". ...
- 最适合作为Java基础面试题之Singleton模式
看似只是最简单的一种设计模式,可细细挖掘,static.synchronized.volatile关键字.内部类.对象克隆.序列化.枚举类型.反射和类加载机制等基础却又不易理解透彻的Java知识纷纷呼 ...
- java基础练习 字符串,控制流,日历,日期等
1,对基本控制流程的一些练习 package org.base.practice3; import org.junit.Test; /** * Created with IntelliJ IDEA. ...
- Java基础知识【下】( 转载)
http://blog.csdn.net/silentbalanceyh/article/details/4608360 (最终还是决定重新写一份Java基础相关的内容,原来因为在写这一个章节的时候没 ...
- Java基础知识【上】(转载)
http://blog.csdn.net/silentbalanceyh/article/details/4608272 (最终还是决定重新写一份Java基础相关的内容,原来因为在写这一个章节的时候没 ...
- java基础学习03(java基础程序设计)
java基础程序设计 一.完成的目标 1. 掌握java中的数据类型划分 2. 8种基本数据类型的使用及数据类型转换 3. 位运算.运算符.表达式 4. 判断.循环语句的使用 5. break和con ...
随机推荐
- Composer创建和发送HTTP Request
Fiddler Composer的功能就是用来创建HTTP Request 然后发送. 你可以自定义一个Request, 也可以手写一个Request, 你甚至可以在Web会话列表中拖拽一个已有的Re ...
- Ubuntu 16.04 升级 PHP 版本至 7.1
安装swoole扩展,怎么安装到7.0下去了,我本来编译的版本是7.19版本,但是没吃 升级步骤 $ sudo add-apt-repository ppa:ondrej/php $ sudo apt ...
- 利用lsof恢复进程占用的文件
说明:经常会遇到这种情况,没有使用正确的方式清理进程占用的文件,比如日志.导致空间并没有释放.也有的时候需要恢复进程占用的文件. 解决方式 lsof |grep del # 找出自己要恢复的文件名称. ...
- TPYBoard v102的GPIO使用用法
引脚介绍 引脚是控制I/O引脚的基本对象.它可以设置引脚输入.输出等的方式或者获取和设置数字逻辑电平的.对于模拟控制引脚,请参见ADC类.TPYBoard一共有68根针脚,26个3.3V,VIN接口: ...
- IDEA第六章----快捷键
第一节:解决快捷键冲突 idea支持很多快捷键,这样就导致了很多快捷键和其他应用冲突,所以需要把其他应用的快捷键去掉,下面以输入法和QQ为例. QQ我就留下了提取消息和截图,这个是个人习惯问题. 第二 ...
- flex盒模型 详细解析
flex盒模型 详细解析 移动端页面布局,采用盒模型布局,效果很好 /* ============================================================ ...
- Linux 常见命令示例【一】
查看端口占用 [查看目前系统上已在监听的网络联机及其pid netstat –tlnp] 文件挂载 Linux与windows文件传输(三方软件:secureCRT, WINscp) 1)sftp S ...
- 【Tarjan】洛谷P3379 Tarjan求LCA
题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...
- 【转载】漫谈HADOOP HDFS BALANCER
Hadoop的HDFS集群非常容易出现机器与机器之间磁盘利用率不平衡的情况,比如集群中添加新的数据节点.当HDFS出现不平衡状况的时候,将引发很多问题,比如MR程序无法很好地利用本地计算的优势,机器之 ...
- ionic build android 中的报错详细原因以及解决方法
一.执行打包命令 ionic build android 1.报错: 原因: 其实也并非报错,但是会一直在下载gradle,由于网络或者其他原因,导致下载比较慢, 解决方案: 手动下载gradle,并 ...