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 extendscom.google.protobuf.GeneratedMessageimplements 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方法。

转载于:https://www.cnblogs.com/onlysun/p/4569462.html

protobuf java基础相关推荐

  1. 八股文(Java基础部分)

    文章目录 一.基础概念与常识 1. Java语言有哪些特点? 2. JVM JDK JRE 3. 字节码 4. JAVA程序从源代码到运行 5.AOT(Ahead of Time Compilatio ...

  2. 《Java 后端面试经》Java 基础篇

    <Java 后端面试经>专栏文章索引: <Java 后端面试经>Java 基础篇 <Java 后端面试经>Java EE 篇 <Java 后端面试经>数 ...

  3. java基础技能清单

    一.java基础 1.java集合框架学习,数据基本类型学习,Integer缓存等问题,常见面试题.对ArrayList和Collecttion.binarySerch()源码进行学习.DelayQu ...

  4. Java基础总结(内部版)

    Java基础总结  琥魄 浏览 4 2016-07-28 10:45:38 发表于: 网商银行技术博客 >> Java技术 编辑 删除 Java核心技术Java  修改标签  标签历史 阿 ...

  5. Java八股文(Java基础面试题)

    JDK.JRE.JVM 三者之间的关系? JDK(Java Development Kit):是Java开发工具包,是整个Java的核心,包括了Java运行环境JRE.Java工具和Java基础类库. ...

  6. 【秋招之Java基础】

    秋招之Java基础 ` 提示:2023秋招八股之Java基础部门. 提示:这就开始了... 文章目录 秋招之Java基础 前言 一.Java基础 1.Java语言的跨平台性 2.Java中的基本数据类 ...

  7. 【2022】Java基础面试真题

    文章目录 1. Java基础 1.1 为什么Java代码可以实现一次编写.到处运行? 1.2 一个Java文件里可以有多个类吗(不含内部类)? 1.3 说一说你对Java访问权限的了解 1.4 介绍一 ...

  8. 计算机语言之java基础知识一

    在家已经待了一个多星期了,最近学到的东西一直没有梳理,这次变梳理边分享出来,说是分享其实就是搬运一下. Java 基础知识 基本数据类型 问:7 种基本数据类型:整型.浮点型.布尔型.字符型? 答:四 ...

  9. Java基础入门语法和安装

    1. Java概述 1.1 Java语言背景介绍(了解) 语言:人与人交流沟通的表达方式 计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言 Java语言是美国Sun公司(Stanford Un ...

最新文章

  1. 4 种主流的 API 架构风格对比
  2. shell 登录mysql 然后quit_使用工具Xshell实现在linux上登录mysql和退出mysql的相关操作讲解...
  3. 深度学习(DL)与卷积神经网络(CNN)学习笔记随笔-01-CNN基础知识点
  4. mysql 布尔型盲注,SQL注入之布尔型注入(MySQL)
  5. 【Tools】MarkDown教程(三)-MarkDown表格和公式
  6. 基于'sessionStorage'与'userData'的类session存储
  7. 想要换壁纸,看这个网站就够了!
  8. ArcMap操作技巧
  9. PHP 根据年月返回这个月的第一天时间戳和这个月的最后一天时间戳
  10. 极光推送 简书android,极光推送 (具体步骤,指导操作,推送成功)
  11. HDU-1869 六度分离 (矩阵的幂运算求通路个数)
  12. 微信小程序开发之——音乐播放器-播放器(3.4)
  13. 分层测试(Layered Testing Approach)
  14. 海思3559A的一些工具探索尝试
  15. 正则表达式 匹配任意长度的字符
  16. Java数据可视化 (JavaFX, Apache ECharts)
  17. 疫情后推动出行即服务
  18. springboot毕业设计 基于springboot在线视频点播系统毕业设计设计与实现参考
  19. 3D模型欣赏:康林博克战士 科幻 游戏角色【3D游戏建模教程】
  20. 【Unity】超级坦克大战(七)选择关卡界面

热门文章

  1. python属性访问权限_python 面向对象-访问权限修饰符
  2. mysql测试表格的年龄的语句是_MySQL查询语句练习题,测试基本够用了
  3. linux 后端的进程,linux后台启动进程
  4. mysql 连接 中文_大佬们E语言连接MYSQL输出中文乱码怎么破
  5. CleanFlight 源码分析 一,任务调度
  6. php pdo无法使用,php - php-无法使用PDO连接到数据库 - SO中文参考 - www.soinside.com
  7. java派生类如何使用_继承:使用基类或派生类来做东西[关闭]
  8. python中没有switch-case_Python为什么没有switch/case语句?
  9. 如果诸葛亮用C++写出师表。。。。
  10. VHDL简易电子琴的设计