Protocol Buffer Basics: Java Proto Buffer基础:java

This tutorial provides a basic Java programmer's introduction to working with protocol buffers. By walking through creating a simple example application, it shows you how to

这个教程提供了一个java程序员的使用protocol buffer的基础教程。通过(walking through)创建一个简单的事例应用程序,他展示给你如何:

  • Define message formats in a .proto file. 
  • 在.proto文件里定义消息格式。
  • Use the protocol buffer compiler.
  • 使用protocol buffer编译器
  • Use the Java protocol buffer API to write and read messages. 
  • 使用java protocol buffer的api读写消息

This isn't a comprehensive guide to using protocol buffers in Java. For more detailed reference information, see the Protocol Buffer Language Guide (proto2), the Protocol Buffer Language Guide (proto3), the Java API Reference, the Java Generated Code Guide, and the Encoding Reference.

这不是在java中使用protocol buffer的一个综合的指导。更多参考信息情况Protocol Buffer Language Guide (proto2)Protocol Buffer Language Guide (proto3), the Java API Reference, Java Generated Code Guide

 

The Problem Domain

The example we're going to use is a very simple "address book" application that can read and write people's contact details to and from a file. Each person in the address book has a name, an ID, an email address, and a contact phone number.

我们将要使用的例子是一个非常简单的“地址簿”应用程序,他可以从文件里读取和写入人们的详细联系信息。在地址薄里的每个人有一个名字、id、email地址和一个联系电话。

How do you serialize and retrieve structured data like this? There are a few ways to solve this problem:

怎样像上面描述的那样去序列化和获取结构化数据?有很多方式去解决这个问题。

  • Use Java Serialization. This is the default approach since it's built into the language, but it has a host of well-known problems (see Effective Java, by Josh Bloch pp. 213), and also doesn't work very well if you need to share data with applications written in C++ or Python

    使用java序列化。这是默认的方法,因为他是内置于语言的,但是他有 许多众所周知的问题(参考Effective java, by Josh Bloch pp.213),并且如果你想要和用c++或Python写的应用程序分享数据,这种方式也不能很好的工作。

  • You can invent an ad-hoc way to encode the data items into a single string – such as encoding 4 ints as "12:3:-23:67". This is a simple and flexible approach, although it does require writing one-off encoding and parsing code, and the parsing imposes a small run-time cost. This works best for encoding very simple data.

    你可以发明一个临时的方法去把数据项编码成单一的字符串。例如把4个整数像这样编码"12:3:-23:67".这是一个简单和灵活的方法,尽管他确实需要写一次性编码和解码的代码,并且解析会带来一个小的运行时成本。对于非常简单的数据,这种方法工作的很好。

  

  • Serialize the data to XML. This approach can be very attractive since XML is (sort of) human readable and there are binding libraries for lots of languages. This can be a good choice if you want to share data with other applications/projects. However, XML is notoriously space intensive, and encoding/decoding it can impose a huge performance penalty on applications. Also, navigating an XML DOM tree is considerably more complicated than navigating simple fields in a class normally would be.

    把数据序列化成XML。这种方法非常有吸引力,因为XML是人类可读的,并且对很多语言,都有现成的类库可用。如果你想和其他应用程序/项目分享数据,这是很好的选择。然而XML最臭名昭著的是空间密集(浪费),并且编码和解码xml,将会给用程序带来巨大的性能损失。而且,相比于查找类中的简单字段,查找XMLDOM树是更加的复杂。

 

 Instead of these options, you can use protocol buffers. Protocol buffers are the flexible, efficient, automated solution to solve exactly this problem. With protocol buffers, you write a .proto description of the data structure you wish to store. From that, the protocol buffer compiler creates a class that implements automatic encoding and parsing of the protocol buffer data with an efficient binary format. The generated class provides getters and setters for the fields that make up a protocol buffer and takes care of the details of reading and writing the protocol buffer as a unit. Importantly, the protocol buffer format supports the idea of extending the format over time in such a way that the code can still read data encoded with the old format.

作为这些以上选项的替代,你可以使用protocol buffers。对于完全正确的解这类问题,Protocol buffers是灵活的,高效的自动化的解决方案。使用protocol buffers,你写一个你要存储的结构化数据的.proto描述。protocol buffer会根据.proto文件创建一个类,这个类实现了使用高效的二进制格式对protocol buffer数据的自动编码和解码。生成的类给组成protocol buffer的字段提供geter和seter,并且作为一个单元,负责读取和写入protocol buffer的详细信息。protocol buffer格式支持随时间扩展格式的想法,用这种方法,代码仍然可以读取使用旧格式编码的数据。

Where to Find the Example Code

哪里找实例代码

The example code is included in the source code package, under the "examples" directory. Download it here.

实例代码包含在源码包里。在examples目录下。在这里下载。

Defining Your Protocol Format

定义你的protocol格式

To create your address book application, you'll need to start with a .proto file. The definitions in a .proto file are simple: you add a message for each data structure you want to serialize, then specify a name and a type for each field in the message. Here is the .proto file that defines your messages, addressbook.proto.

为了创建一个地址簿应用程序。你需要开始于一个.proto文件。在.proto文件里的定义是简单的:你为每一个你想序列化的数据结构增加一个message,然后为message里的每一个字段指定一个名字和类型。下面的代码是一个定义你的消息的.proto文件:addressbook.proto

syntax = "proto2";

package tutorial;

option java_multiple_files = true;
option java_package = "com.example.tutorial.protos";
option java_outer_classname = "AddressBookProtos";

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

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

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

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

 

 

 As you can see, the syntax is similar to C++ or Java. Let's go through each part of the file and see what it does.

如你所视,语法很像C++或者java。让我们浏览文件的每一部分,看看它做了什么。

The .proto file starts with a package declaration, which helps to prevent naming conflicts between different projects. In Java, the package name is used as the Java package unless you have explicitly specified a java_package, as we have here. Even if you do provide a java_package, you should still define a normal package as well to avoid name collisions in the Protocol Buffers name space as well as in non-Java languages.

这个.proto文件开始于一个package定义。它帮助阻止不同工程的名称冲突。在java里,这个package的名字通常作为java包名,除非你明确的指定了java_package,正如我们在这里做的一样。如果你确实提供了一个java_package,你仍需要定义一个常用的package,以免在protocol buffers的名称空间以及非java语言里造成名称冲突。

After the package declaration, you can see three options that are Java-specific: java_multiple_filesjava_package, and java_outer_classnamejava_package specifies in what Java package name your generated classes should live. If you don't specify this explicitly, it simply matches the package name given by the package declaration, but these names usually aren't appropriate Java package names (since they usually don't start with a domain name). The java_outer_classname option defines the class name of the wrapper class which will represent this file. If you don't give a java_outer_classname explicitly, it will be generated by converting the file name to upper camel case. For example, "my_proto.proto" would, by default, use "MyProto" as the wrapper class name. The java_multiple_files = true option enables generating a separate .java file for each generated class (instead of the legacy behavior of generating a single .java file for the wrapper class, using the wrapper class as an outer class, and nesting all the other classes inside the wrapper class).

在package定义之后,你可以看到三个选项,他们是特定于Java的: java_multiple_filesjava_package, 和java_outer_classname。java_package明确规定了你生产的类的java报名应该存在的地方,如果你没有明确指定,编译器起会简单的匹配package定义的包名,但是这些名字通常不适合java包名(因为通常package的名字开头部分不是域名)。java_outer_classname选项定义包装类的类名,这个包装类的类名代表了这个文件。如果没没有明确的给出一个java_outer_classname,编译器将根据转换文件名,并且把文件名转换成大驼峰来生成包装类名。例如"my_proto.proto"默认情况下将使用MyProto作为包装类名。java_multiple_files=true选项,使得编译器为每个要生产的类生成一个独立的.java文件(而不是继承为包装类生成一个单.java文件,使用包装类作为输出类,并且嵌入所有的其他类到包装类中)。

Next, you have your message definitions. A message is just an aggregate containing a set of typed fields. Many standard simple data types are available as field types, including boolint32floatdouble, and string. You can also add further structure to your messages by using other message types as field types – in the above example the Person message contains PhoneNumber messages, while the AddressBook message contains Person messages. You can even define message types nested inside other messages – as you can see, the PhoneNumber type is defined inside Person. You can also define enum types if you want one of your fields to have one of a predefined list of values – here you want to specify that a phone number can be one of the following phone types: MOBILEHOME, or WORK.

接下来,你有了一个消息定义,这个消息是一个包含一套类型字段的集合。很多标准的简单数据类型可用于字段类型,包括bool,int32,float,double和string。你也可以通过使用其他消息类型作为一个类型字段来增加深层的结构。在上面的例子中,Person消息包含了PhoneNumber消息,同样,AddressBook消息包含了Person消息。你甚至可以dinginess消息类型嵌入到其他消息,正如你看到的,PhoneNumber类型被定义在了Person内部。如果你想要你的一个字段是一个预定义值清单中的一个,你也可以定义enum类型。在这里你想指定的一个phone number可能是下面的电话类型MOBILE,HOME,WORK中的一个。

The " = 1", " = 2" markers on each element identify the unique "tag" that field uses in the binary encoding. Tag numbers 1-15 require one less byte to encode than higher numbers, so as an optimization you can decide to use those tags for the commonly used or repeated elements, leaving tags 16 and higher for less-commonly used optional elements. Each element in a repeated field requires re-encoding the tag number, so repeated fields are particularly good candidates for this optimization.

在没个元素上的=1,=2标记,显示了在二进制编码里字段使用的唯一标识。标识编号1-15相比高一些编号需要一个较少的字节。所以,作为一种优化,你可以决定为常用的或者重复的元素使用这些1-15的标记。剩下的大于16的用于不常用的可选的元素。每一个在重复字段里的元素,需要重编标识编号。所以对于这种优化,重复字段是相当好的选择。

Each field must be annotated with one of the following modifiers:

每一个字段必须使用下面的修饰符之一来声明。

  • optional: the field may or may not be set. If an optional field value isn't set, a default value is used. For simple types, you can specify your own default value, as we've done for the phone number type in the example. Otherwise, a system default is used: zero for numeric types, the empty string for strings, false for bools. For embedded messages, the default value is always the "default instance" or "prototype" of the message, which has none of its fields set. Calling the accessor to get the value of an optional (or required) field which has not been explicitly set always returns that field's default value.

  可选项:optional修饰符修饰的字段可以设置也可以不设置。如果optional字段没有被设置,一个默认值被使用。对于 简单类型,你可以指定你自己的默认值, 就像在例子中我们针对phone number类型做的一样。另外系统使用的默认值:数字类型0,字符串类型是空字符串,布尔型的默认值是false。对于嵌入的消息默认值总是一个消息的默认实例或者原型,这个实例没有设置任何字段。调用访问器来获取一个可选的或者必选的没有明确指定设置的字段,总是返回字段的默认值。

  • repeated: the field may be repeated any number of times (including zero). The order of the repeated values will be preserved in the protocol buffer. Think of repeated fields as dynamically sized arrays.

  重复的:这个字段可以重复任何次数包括0次。重复值的顺序被保存金protocolbuffer。想一想重复字段作为一个动态大小的数组。

  • required: a value for the field must be provided, otherwise the message will be considered "uninitialized". Trying to build an uninitialized message will throw a RuntimeException. Parsing an uninitialized message will throw an IOException. Other than this, a required field behaves exactly like an optional field.

  必选项:必须为这个字段提供值。否则消息被视为没有初始化。尝试构建一个没有初始化的消息,将引发一个RuntimeException(运行时异常)。解析一个没有初始化的消息,将抛出一个IOException。除此之外,必选字段的行为和可选字段的行为类似。

Required Is Forever You should be very careful about marking fields as required. If at some point you wish to stop writing or sending a required field, it will be problematic to change the field to an optional field – old readers will consider messages without this field to be incomplete and may reject or drop them unintentionally. You should consider writing application-specific custom validation routines for your buffers instead. Within Google, required fields are strongly disfavored; most messages defined in proto2 syntax use optional and repeated only. (Proto3 does not support required fields at all.)

Required修饰符是永久的,你应该非常小心使用required修饰字段。如果在某个时候,你想去停止写入和发送一个required 字段,把这个字段改编成optional字段是有问题的。老的读取器将认为没有这个字段的消息是不完整的,并且可能驳回或者删除这个消息。你应该考虑写编写指定的应用,定制验证规则。在谷歌内部,required字段是非常不受欢迎的。大部用proto2语法定义的消息只使用optional和repeated(proto3 完全不再支持required字段)。

You'll find a complete guide to writing .proto files – including all the possible field types – in the Protocol Buffer Language Guide. Don't go looking for facilities similar to class inheritance, though – protocol buffers don't do that.

在 Protocol Buffer Language Guide中,你将发现一个编写.proto文件的完整向导,包含所有可能类型字段。不要查找类似于类继承的特色功能。因为protocol buffers不支持那样操作。

 

Compiling Your Protocol Buffers

编译你的protocol buffers

Now that you have a .proto, the next thing you need to do is generate the classes you'll need to read and write AddressBook (and hence Person and PhoneNumber) messages. To do this, you need to run the protocol buffer compiler protoc on your .proto:

选择你有了一个.proto文件,你需要做的下一个事情是生成你将要读写AddressBook消息的类。你需要运行protocol buffer编译器protoc带着你的.proto文件。

  1. If you haven't installed the compiler, download the package and follow the instructions in the README.

  如果你没有安装编译器,下载并且按照readme里的说明操作。

 

2.Now run the compiler, specifying the source directory (where your application's source code lives – the current directory is used if you don't provide a value), the destination directory (where you want the generated code to go; often the same as $SRC_DIR), and the path to your .proto. In this case, you...:

 现在运行编译器,指定源目录(你应用程序的源代码所在的目录,如果没有提供源目录,当前目录将被使用),目标目录(你想要生成的代码所在的目录,通常和¥SRC_DIR一样)并且你的.proto文件所在的目录。在这种情况下
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
Because you want Java classes, you use the --java_out option – similar options are provided for other supported languages.

 

因为你想要的是java类,所以你使用--java_out,其他支持的语言也提供了相似的选项。

This generates a com/example/tutorial/protos/ subdirectory in your specified destination directory, containing a few generated .java files.

这里,在你指定的目录里生成了一个 a com/example/tutorial/protos/子目录。包含一些生成的.java文件。

The Protocol Buffer API

Protocol Buffer 的API

Let's look at some of the generated code and see what classes and methods the compiler has created for you. If you look in com/example/tutorial/protos/, you can see that it contains .java files defining a class for each message you specified in addressbook.proto. Each class has its own Builder class that you use to create instances of that class. You can find out more about builders in the Builders vs. Messages section below.

让我们看一看生产的代码,并且看一下编译器为你生产了什么类和方法。如果你查看 com/example/tutorial/proto,你能看到他包含了一些.java文件,每个文件为你在addressbook.proto文件里指定的每个消息定义了一个类每个类都有自己的Builder类,你可以使用builder类创建类的实例。你可以在下面的Builder vs Message章节,找到更多的关于builder的内容。

Both messages and builders have auto-generated accessor methods for each field of the message; messages have only getters while builders have both getters and setters. Here are some of the accessors for the Person class (implementations omitted for brevity):

消息和访问器为每个消息的字段都自动生成了访问器方法。message只有getters同时builer既有getter也有setter。这里是Person类的访问器。

// required string name = 1;
public boolean hasName();
public String getName();

// required int32 id = 2;
public boolean hasId();
public int getId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();

// repeated .tutorial.Person.PhoneNumber phones = 4;
public List<PhoneNumber> getPhonesList();
public int getPhonesCount();
public PhoneNumber getPhones(int index);

 

 Meanwhile, Person.Builder has the same getters plus setters:

同时,Persion.Builder有getter和setter

// 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 phones = 4;
public List<PhoneNumber> getPhonesList();
public int getPhonesCount();
public PhoneNumber getPhones(int index);
public Builder setPhones(int index, PhoneNumber value);
public Builder addPhones(PhoneNumber value);
public Builder addAllPhones(Iterable<PhoneNumber> value);
public Builder clearPhones();

 

 As you can see, there are simple JavaBeans-style getters and setters for each field. There are also has getters for each singular field which return true if that field has been set. Finally, each field has a clear method that un-sets the field back to its empty state.

如你所视,每一个字段都有简单的javaBean样式的getter和setter。每一个字段也有一个has访问器。如果字段被赋值。那么has访问器将返回true。每个字段都有一个clear方法,他清空字段到他的空状态。

Repeated fields have some extra methods – a Count method (which is just shorthand for the list's size), getters and setters which get or set a specific element of the list by index, an add method which appends a new element to the list, and an addAll method which adds an entire container full of elements to the list.

重复字段有一个额外的Count方法,他只是简list大小的简写。getter和setter访问器,他通过index来获取或者设置列表的个指定的元素。add方法这个方法增加一个新的元素到列表里。addAll方法,他的作用是增加一整个充满元素的容器到列表里。

 

Notice how these accessor methods use camel-case naming, even though the .proto file uses lowercase-with-underscores. This transformation is done automatically by the protocol buffer compiler so that the generated classes match standard Java style conventions. You should always use lowercase-with-underscores for field names in your .proto files; this ensures good naming practice in all the generated languages. See the style guide for more on good .proto style.

注意:这些访问方法怎么如何使用驼峰命名,尽管,.proto文件使用小写和下划线明明。这个转换被protocol buffer编译器自动完成,以便生成的类符合标准的Java样式的约定。在你的.proto文件里,你应该总是使用带下划线的小写来命名字段;这确保在所有的生成语言中都确保有好的命名实践。更多关于好的.proto样式,查看style guide。

For more information on exactly what members the protocol compiler generates for any particular field definition, see the Java generated code reference.

关于协议编译器为任何特定字段定义生成哪些成员的详细信息,查看java generated code reference

Enums and Nested Classes

枚举和内嵌类

The generated code includes a PhoneType enum, nested within Person:

生成的代码包含了一个内嵌到Person类里面的PhoneType的枚举,。

 

 

public static enum PhoneType {
  MOBILE(0, 0),
  HOME(1, 1),
  WORK(2, 2),
  ;
  ...
}

 

 

The nested type Person.PhoneNumber is generated, as you'd expect, as a nested class within Person.

这个嵌套的类型Persion.PhoneNumber被生成,如你所料,做为Person的嵌套类。

 

Builders vs. Messages

 

The message classes generated by the protocol buffer compiler are all immutable. Once a message object is constructed, it cannot be modified, just like a Java String. To construct a message, you must first construct a builder, set any fields you want to set to your chosen values, then call the builder's build() method.

被protocol buffer编译器生成的message类都是不可改变的。一旦消息对象被构建,他不能被修改,就像一个java字符串一样。为了构造一个message,你必须首先构造一个builder,把你想要设置的任何字段设置成你选择的值,然后调用builder的build方法。

 

You may have noticed that each method of the builder which modifies the message returns another builder. The returned object is actually the same builder on which you called the method. It is returned for convenience so that you can string several setters together on a single line of code.

你可能已经注意到,builder的每一个修改消息的方法,都返回另一个builder。你调用的方法所在的builder与这个方法所返回的builder是同一个builder。返回builder是为了方便,这样你可以在一行代码中链式调用几个setter。

Here's an example of how you would create an instance of Person:

这是一个你将会怎样创建一个Person的实例的例子。

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .addPhones(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.HOME))
    .build();

 

 

Standard Message Methods

标准消息方法

Each message and builder class also contains a number of other methods that let you check or manipulate the entire message, including:

每一个message和builder类也包含一些其他的方法,这些方法可以让你检查或者操作整个message,包括:

  • isInitialized(): checks if all the required fields have been set.
  •               检查是否所有的字段是否已经设置。
  • toString(): returns a human-readable representation of the message, particularly useful for debugging.
  • 返回一个刻度的消息的表现形式,调试的时候非常有用。
  • mergeFrom(Message other): (builder only) merges the contents of other into this message, overwriting singular scalar fields, merging composite fields, and concatenating repeated fields.
  • (仅限生成器)将other的内容合并到此消息中,覆盖单个标量字段,合并复合字段,并串联重复字段。
  • clear(): (builder only) clears all the fields back to the empty state.
  • 清除所有的字段到空状态。

These methods implement the Message and Message.Builder interfaces shared by all Java messages and builders. For more information, see the complete API documentation for Message.

这些方法实现了Message和Message.Builder的被所有javaMessage和builder共享的接口。有关更多信息,请看complete API documentation for Message.

Parsing and Serialization

解析和序列化

Finally, each protocol buffer class has methods for writing and reading messages of your chosen type using the protocol buffer binary format. These include:

最终,每一个protocol buffer类有

  • byte[] toByteArray();: serializes the message and returns a byte array containing its raw bytes.
  • byte[] toByteArray() 序列化消息并且返回一个byte数组,包含了它的原始的byte
  • static Person parseFrom(byte[] data);: parses a message from the given byte array.
  • static Person parseFrom(byte[] data);从给定的byte数组中解析消息
  • void writeTo(OutputStream output);: serializes the message and writes it to an OutputStream.
  • void writeTo(OutputStream output);序列化消息,并且把它写到OutputStream流中
  • static Person parseFrom(InputStream input);: reads and parses a message from an InputStream.
  • static Person parseFrom(InputStream input);:从InputStream中读取并解析消息。

These are just a couple of the options provided for parsing and serialization. Again, see the Message API reference for a complete list.

这些知识提供给解析和序列化几个选项,再一次查看 Message API reference ,查看完整的列表。

Protocol Buffers and Object Oriented Design Protocol buffer classes are basically data holders (like structs in C) that don't provide additional functionality; they don't make good first class citizens in an object model. If you want to add richer behavior to a generated class, the best way to do this is to wrap the generated protocol buffer class in an application-specific class. Wrapping protocol buffers is also a good idea if you don't have control over the design of the .proto file (if, say, you're reusing one from another project). In that case, you can use the wrapper class to craft an interface better suited to the unique environment of your application: hiding some data and methods, exposing convenience functions, etc. You should never add behavior to the generated classes by inheriting from them. This will break internal mechanisms and is not good object-oriented practice anyway.

Protocol Buffers和面向对象的设计:Protocol buffer类是基础的数据持有者(像c语言中的结构)它们不提供额外的功能。他们不会在对象模型中成为优秀的一等公民。如果你想对生产的类增加丰富的行为,最好的做法是在应用程序指定的类中包装生成的protocol buffer类。如果没有掌控.proto文件的设计,包装protocol buffers也是一个号注意。(例如,你正在服用一个来自于其他工程中的类)。在那种情况下,你可以使用包装类去制作一个接口,更好的适合你应用的独特环境。隐藏一些数据和方法,暴露方便的函数。例如,你永远不应该通过继承来增加行为到生成的类。这将破坏内部机制并且无论如何都不是好的面向对象实践。

Writing A Message

Now let's try using your protocol buffer classes. The first thing you want your address book application to be able to do is write personal details to your address book file. To do this, you need to create and populate instances of your protocol buffer classes and then write them to an output stream.

现在,让我们尝试使用你的protocol buffer类。你想要你的地址薄应用能做的第一件事是往你的地址簿文件里写个人详情。为此,你需要创建和填充你的protocol buffer 类的实例,然后把他们写入输出流。

import com.example.tutorial.protos.AddressBook;
import com.example.tutorial.protos.Person;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;

class AddPerson {
  // This function fills in a Person message based on user input.
  static Person PromptForAddress(BufferedReader stdin,
                                 PrintStream stdout) throws IOException {
    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.length() > 0) {
      person.setEmail(email);
    }

    while (true) {
      stdout.print("Enter a phone number (or leave blank to finish): ");
      String number = stdin.readLine();
      if (number.length() == 0) {
        break;
      }

      Person.PhoneNumber.Builder phoneNumber =
        Person.PhoneNumber.newBuilder().setNumber(number);

      stdout.print("Is this a mobile, home, or work phone? ");
      String type = stdin.readLine();
      if (type.equals("mobile")) {
        phoneNumber.setType(Person.PhoneType.MOBILE);
      } else if (type.equals("home")) {
        phoneNumber.setType(Person.PhoneType.HOME);
      } else if (type.equals("work")) {
        phoneNumber.setType(Person.PhoneType.WORK);
      } else {
        stdout.println("Unknown phone type.  Using default.");
      }

      person.addPhones(phoneNumber);
    }

    return person.build();
  }

  // Main function:  Reads the entire address book from a file,
  //   adds one person based on user input, then writes it back out to the same
  //   file.
  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 addressBook = AddressBook.newBuilder();

    // Read the existing address book.
    try {
      addressBook.mergeFrom(new FileInputStream(args[0]));
    } catch (FileNotFoundException e) {
      System.out.println(args[0] + ": File not found.  Creating a new file.");
    }

    // Add an address.
    addressBook.addPerson(
      PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
                       System.out));

    // Write the new address book back to disk.
    FileOutputStream output = new FileOutputStream(args[0]);
    addressBook.build().writeTo(output);
    output.close();
  }
}

Reading A Message

Of course, an address book wouldn't be much use if you couldn't get any information out of it! This example reads the file created by the above example and prints all the information in it.

当然,如果你不能从中获取任何信息,那么地直播将不是很有用。这个例子读取上面例子创建的文件,并且打印出里面的所有信息。

import com.example.tutorial.protos.AddressBook;
import com.example.tutorial.protos.Person;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;

class ListPeople {
  // Iterates though all people in the AddressBook and prints info about them.
  static void Print(AddressBook addressBook) {
    for (Person person: addressBook.getPeopleList()) {
      System.out.println("Person ID: " + person.getId());
      System.out.println("  Name: " + person.getName());
      if (person.hasEmail()) {
        System.out.println("  E-mail address: " + person.getEmail());
      }

      for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
        switch (phoneNumber.getType()) {
          case MOBILE:
            System.out.print("  Mobile phone #: ");
            break;
          case HOME:
            System.out.print("  Home phone #: ");
            break;
          case WORK:
            System.out.print("  Work phone #: ");
            break;
        }
        System.out.println(phoneNumber.getNumber());
      }
    }
  }

  // Main function:  Reads the entire address book from a file and prints all
  //   the information inside.
  public static void main(String[] args) throws Exception {
    if (args.length != 1) {
      System.err.println("Usage:  ListPeople ADDRESS_BOOK_FILE");
      System.exit(-1);
    }

    // Read the existing address book.
    AddressBook addressBook =
      AddressBook.parseFrom(new FileInputStream(args[0]));

    Print(addressBook);
  }
}

Extending a Protocol Buffer扩展protocol Buffer

Sooner or later after you release the code that uses your protocol buffer, you will undoubtedly want to "improve" the protocol buffer's definition. If you want your new buffers to be backwards-compatible, and your old buffers to be forward-compatible – and you almost certainly do want this – then there are some rules you need to follow. In the new version of the protocol buffer:

在你发布了你使用的protocol buffer的代码之后,迟早,你将无疑的想要改善protocol buffer的定义。如果你想要你的新的buffers是向后兼容的,并且你的老的buffer是向前兼容,你几乎确实想要这个。那么有一些规则需要你遵守。在新的protocolbuffer版本里:

  • you must not change the tag numbers of any existing fields.你必须不能改变任何一家存在的字段的tag number
  • you must not add or delete any required fields.你必须不能增加和删除任何required字段。
  • you may delete optional or repeated fields.可以可以删除optional或者repeated的字段
  • you may add new optional or repeated fields but you must use fresh tag numbers (that is, tag numbers that were never used in this protocol buffer, not even by deleted fields).你可以增加新的optional或者repeated字段,但是你必须使用新的tag number。(那就是,tag number在protocol buffer里从来没有使用过,甚至是被删除字段的tag number)。

(There are some exceptions to these rules, but they are rarely used.) 这些规则有一些例外,但是他们很少被使用。

If you follow these rules, old code will happily read new messages and simply ignore any new fields. To the old code, optional fields that were deleted will simply have their default value, and deleted repeated fields will be empty. New code will also transparently read old messages. However, keep in mind that new optional fields will not be present in old messages, so you will need to either check explicitly whether they're set with has_, or provide a reasonable default value in your .proto file with [default = value] after the tag number. If the default value is not specified for an optional element, a type-specific default value is used instead: for strings, the default value is the empty string. For booleans, the default value is false. For numeric types, the default value is zero. Note also that if you added a new repeated field, your new code will not be able to tell whether it was left empty (by new code) or never set at all (by old code) since there is no has_ flag for it.

如果你遵守这些规则,老的代码将顺利的读取新的消息并且简单的胡烈任何新的字段。对于老的代码,被删除optional字段将简单的拥有他们的默认值,并且被删除的repeated字段,将是空值。新代码也将透明的读取老的消息。然而,注意,新的otional字段在老的消息里将不被呈现,所以你需要明确检查他们是否被设置为has_,或者在你的.proto文件里在tag number之后使用[default=value]提供一个合理的默认值。如果optional字段没有被指定默认值,改为使用一个特定于类型的默认值。对于string,默认值是空字符串,booleans默认值是false,对于数字类型,默认值是0。注意如果你增加一个repeated字段,你的新代码将不能分辨是否他是留空或者从没有设置,因为他没有has标记。

Advanced Usage 高级用法

 

Protocol buffers have uses that go beyond simple accessors and serialization. Be sure to explore the Java API reference to see what else you can do with them.

Protocol buffer有超越了简单的访问器和序列化之外的用途。务必探索Java API参考,来看看你还可以使用它们做什么。

One key feature provided by protocol message classes is reflection. You can iterate over the fields of a message and manipulate their values without writing your code against any specific message type. One very useful way to use reflection is for converting protocol messages to and from other encodings, such as XML or JSON. A more advanced use of reflection might be to find differences between two messages of the same type, or to develop a sort of "regular expressions for protocol messages" in which you can write expressions that match certain message contents. If you use your imagination, it's possible to apply Protocol Buffers to a much wider range of problems than you might initially expect!

protocol message累提供的一个关键的特征是反射,你可以遍历消息的字段,并且操作它们的值,不需要针对任何特定消息类型编写代码。使用反射的一个非常有用的方式是将协议消息与其它编码相互转换,例如xml,json。对反射的更高级的使用可能是找到相同类型的两个消息之间的不同。或者是开发一种消息协议的正则表达式,你可以在其中编写表达式来匹配某种消息内容。如果你使用你的想象力,可以将 Protocol Buffers 应用到比您最初期望的,更广泛问题的范围,

Reflection is provided as part of the Message and Message.Builder interfaces.

反射作为message和Message.Builder接口的一部分被提供

posted on 2022-09-17 15:49  360qq  阅读(146)  评论(0编辑  收藏  举报

导航