protobuf学习(2):.proto文件的定义

官网java文档地址:https://developers.google.com/protocol-buffers/docs/javatutorial

概述

本章主要根据官网学习向导初步了解protobuf的使用

为什么要用protobuf

官网通过一个“通讯录”的demo来作为入门,这个demo可以从一个文件读写通讯录人员的联系信息,包括人员的name、ID、email、手机号。

怎么来序列化和检索结构化数据呢?通常有以下几点来处理:

  • 使用Java序列化,这是java内置的一种途径,但是这种途径有一些众所周知的问题(see Effective Java, by Josh Bloch pp. 213),同时由于Java序列化是内置的一种方案,当你需要和其他语音编写的程序传递数据的时候,它是没有办法做到的
  • 您可以自定义一种特别的方法来将数据编码为单个字符串——比如将4个int编码为“12:3:-23:67”。这是一种简单而灵活的方法,尽管它需要编写一次性的编码和解析代码,而且解析会带来少量的运行时开销。这种方法只适用于非常简单的数据编码。
  • 将数据序列化为XML,XML标记语言是人为可读的,很多的语言都是针对XML的类库,可以跨语言、跨平台传递数据,然而XML是出名的空间密集型,XML的编码/解码会给程序带来巨大的性能压力,它的DOM树通常也比一个类的字段复杂的多。

protobuf是灵活的、高效的,是准确解决以上问题的自动化方案。在protobuf中,你需要编写一个.proto文件去描述你要存储的数据结构,根据这个描述文件,protobuf编译器可以创建一个类用于实现自动的编码和从一个有效的二进制数据中解析出protobuf数据。所生成的类为字段组成提供getter和setter方法,作为组成protobuf读和写的单元。更重要的是,当随着时间的推移,protocbuf format支持读取旧的格式编码的数据,换句话说就是支持向前兼容旧代码。

定义protobuf格式

在创建“通讯录”应用之前,需要从创建.proto文件开始。定义.proto文件很简单:为要序列化的每个数据结构添加条message,然后为message中的每个字段指定一个名称和类型。

syntax = "proto2";

package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}
syntax = "proto2"指的是protobuf2的版本

.proto文件以一个包的声明开始(package tutorial;),这有助于防止不同项目之间的命名冲突。在生成Java代码过程中,(package)通常指的是Java package,除非您已经显式地声明了了(java_package),就像我们在这里所做的那样(package tutorial;)。即使您确实提供了(option java_package = "com.example.tutorial";),您也应该定义一个(package tutorial;),以避免protobuf命名空间和非java语言中的名称冲突。

说人话:如果没有显式地定义(java_package),那生成java代码的包名就是(package)所定义的路径,如果定义了(java_package),那就会按照(java_package)的路径来生成包名。但是(package)并不适合用来作为Java的包名,因为Java的包名一般都是 (com. | org. | cn.)

针对于Java特性的声明:(java_package)和(java_outer_classname),用来定位和描述生成的类。(java_package)就不用多说了,(java_outer_classname)会用来作为类名,这个类将包括之后所定义的所有字段或者是类(比如之后定义的message Person、message AddressBook),如果没有显示的指定(java_outer_classname),那么将会根据.proto文件名用驼峰命名法来给生成的Java类命名,如:my_proto.proto生成的类名为MyProto。


message 代表了字段的集合,字段的类型会有bool,int32,float,double,string,还可以通过其他的message类型作为字段进一步的定义message的结构,比如 message Person 包含了 message PhoneNumber ,类似于Java中自定义类型作为另一个类的字段,还可以定义枚举类型。


“= 1”和“= 2”将作为字段在二进制中的一种标记,并不是赋值,1-15将比15以上的标记少一个字节,所以常用的必要字段(required 修饰)用1-15来标记,不怎么常用的非必要字段(optional 修饰)用15以上来标记,这相当于是一种优化,对于repeated 修饰的字段,相当于这个字段是个list集合,protobuf会将这种字段的标记重新编码,这时候用15以上来标记这种字段,优化的效果更好。


  • required修饰一个字段,那这个字段是必传的,否者message会被认为是没有初始化的,构建一个未初始化的message会抛出一个RuntimeException,解析一个未初始化的message会抛出一个IOExcption,除了这些,其他特性与optional修饰一样。
  • optional修饰一个字段,这个字段可以设置也可以不设置初始值,不设置初始值,会被初始化为默认值,数字类型默认值为0,字符串默认值为空字符串,bool类型默认值为false,对于嵌套的类型来说,如实例中的AddressBook ,默认值为这个message的“default instance”或者“prototype”,其内部的字段都是未被设定的,。对于简单类型来说,你可以提供你的默认值,类似于示例中的type字段,其类型为PhoneType ,默认值为HOME。如果调用访问器去获取没有被显示设定的required或者optional修饰的字段,总是返回这个字段的默认值。
  • repeated修饰一个字段,这字段就可以重复多次包括0次,重复值的顺序会被保存在protobuf中,相当于动态长度的数组,类似于Java ArryList。

注意:

required的使用要极其谨慎,如果开始定义了一个字段用required修饰,之后又想改为optional,旧的reader端会认为没有这个字段,认为是不兼容的,会拒绝接收并丢弃这个字段,Google自己的程序员都觉得使用required弊大于利

完整的.proto编写指南在Language Guide(proto2)或者Language Guide(proto3),不要尝试在protobuf中使用继承,protobuf不存在这种特性。

猜你喜欢

转载自blog.csdn.net/qq_25805331/article/details/108350192
今日推荐