文章目录
Protocol Buffer Basics: Java | Protocol Buffers | Google Developers
- 在
.proto
文件中定义消息格式 - 使用协议缓冲区编译器
- 使用Java协议缓冲区API来写入和读取消息
为什么要使用协议缓冲区?
我们将要使用的示例是一个非常简单的“地址簿”应用程序,它可以从文件中读取和写入人们的联系人详细信息。地址簿中的每个人都有一个姓名、一个ID、一个电子邮件地址和一个联系电话号码。
如何序列化和检索这样的结构化数据?有几种方法可以解决这个问题:
- 使用Java序列化。这是默认的方法,因为它是内置于语言中的,但是它有许多众所周知的问题 (参见 Effective Java, by Josh Bloch pp. 213),如果您需要与用C+或Python编写的应用程序共享数据,也不能很好地工作。
- 您可以发明一种特殊的方法将数据项编码为单个字符串,例如将4个INT编码为“12:3:-23:67”。这是一种简单而灵活的方法,尽管它确实需要编写一次性编码和解析代码,并且解析带来了较小的运行时成本。这对编码非常简单的数据最有效。
- 将数据序列化为XML。这种方法非常有吸引力,因为XML(某种程度上)是人类可读的,并且有许多语言的绑定库。如果您想要与其他应用程序/项目共享数据,这可能是一个很好的选择。然而,XML是众所周知的空间密集型,它的编码/解码会给应用程序带来巨大的性能损失。另外,在XML DOM树中导航比在类中导航简单字段要复杂得多。
协议缓冲区是解决这一问题的灵活、高效、自动化的解决方案。使用协议缓冲区,您可以编写一个.proto要存储的数据结构的说明。由此,协议缓冲区编译器创建一个类,该类以高效的二进制格式实现协议缓冲区数据的自动编码和解析。生成的类为组成协议缓冲区的字段提供getter和setter,并负责作为一个单元读取和写入协议缓冲区的详细信息。重要的是,协议缓冲区格式支持随着时间的推移扩展格式的思想,以便代码仍然可以读取旧格式编码的数据。
示例代码下载
示例代码包含在“示例”目录下的源代码包中。在这里下载。
https://developers.google.cn/protocol-buffers/docs/downloads
定义协议格式
要创建通讯簿应用程序,您需要从一个.proto
文件开始。定义一个.proto
文件很简单:对于每个要序列化的数据结构你可以添加一个message,然后为message中的每个字段指定名称和类型。这是.proto
定义您的message的文件,addressbook.proto
.
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;
}
如您所见,语法类似于C+或Java。让我们检查一下文件的每个部分,看看它能做什么。
这个.proto
文件以包声明开始,这有助于防止不同项目之间的命名冲突。在Java中,除非您已经显式地指定了java_package
就像我们在这里一样。即使你提供了java_package
,您仍然应该定义一个正常的package
。此外,为了避免协议缓冲区中的名称冲突,以及在非Java语言中,名称空间也是如此。
在包声明之后,您可以看到两个特定于Java的选项:java_package
和java_outer_classname. java_package
在Java包名称中指定生成的类应该使用的名称。如果没有显式地指定此名称,则它与package
声明,但这些名称通常不是适当的Java包名称(因为它们通常不以域名开头)。这个java_outer_classname
选项定义类名,该类名应包含该文件中的所有类。如果你显式地提供java_outer_classname
,它将通过将文件名转换为CAMEL(驼峰式)大小写来生成。例如,默认情况下,“my_pro.proto”将使用“MyProto”作为外部类名。
接下来,您将得到您的消息定义。消息只是包含一组类型化字段的聚合。许多标准的简单数据类型都可以作为字段类型使用,包括bool
、int32
、float
、double
和string
。还可以将其他消息类型作为字段类型添加到消息中-在上面的示例中,Person
消息包含PhoneNumber
消息,而AddressBook
消息包含Person
消息。您甚至可以定义嵌套在其他消息中的消息类型-正如您所看到的,PhoneNumber
类型是在Person
中定义的。如果您希望您的一个字段具有一个预定义的值列表,您也可以定义enum
类型-在这里,您希望指定电话号码可以是MOBILE
、HOME
或WORK
。
每个元素上的“=1”、“=2”标记标识字段在二进制编码中使用的唯一“tag”。标签号 1-15 比较高的数字需要少一个字节来编码,因此,作为优化,您可以决定将这些标记用于常用的或重复的元素,留下标记16或更高的标记用于较少使用的可选元素。重复字段中的每个元素都需要重新编码标记号,因此重复字段是这种优化的最佳选择。
必须用下列修饰符之一对每个字段进行注释:
-
required
:必须提供字段的值,否则该消息将被视为“未初始化”。试图构建未初始化的消息将引发RuntimeException
。解析未初始化的消息将引发