`

Protobuf java基础

阅读更多

原文地址:http://www.open-open.com/home/space-37924-do-blog-id-5874.html

 

本文档为java编程人员使用protocol buffer提供了一个基本的介绍,通过一个简单的例程进行介绍。通过本文,你可以了解到如下信息:

1、在一个.proto文件中定义一个信息格式.

2、使用protoc命令进行编译,生成java代码.

3、使用Java protocol buffer API进行读写操作.

定义proto文件

以一个地址薄为例,从建立一个.proto文件开始,为需要序列化的数据接口加入一个message属性,在message里面,为每一个字段指定名称和类型,如下所示:

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 phone = 4;

}

 

message AddressBook {

  repeated Person person = 1;

}

正如你所见, c++和Java中message定义的语法类似,下面我们来看看每个部分的意义:

     为了避免命名冲突,.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修饰符。

编译Protocol Buffers文件

既然现在已经有了.proto文件,接下来就需要利用编译器protoc对.proto文件进行编译,生成具体的java类。就可以读取及写入AddressBook、Person及PersonNumber消息了。

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto

$SRC_DIR :表示.proto文件所在目录;$DST_DIR:生成的java代码的文件夹。

编译成功后,会在指定的目录下生成Java代码文件,包含了对属性的操作,下一步就可以通过API进行数据的读写了。

Protocol Buffer API使用

接下来具体看一下所生成的java代码及其中的方法。在AddressBookProtos.java中可以看出,其中的内部类对应的是addressbook.proto中定义的格式。每个类都有它自己的Builder类,通过它即可以创建该类的实例。你可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/javatutorial.html#builders中查阅到更多关于builder的信息。

Messages和Builders都会为每个域创建自动的访问方法,其中messages只有getters,而builders有getters和setters。下面是Person类message的访问方法:

// 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 phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(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文件即可。

枚举和内部类

生成的代码包含了一个枚举类型PhoneType,它属于Person的内部类:

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

PhoneNumber也是作为Person的一个内部类而产生的。

Builders 对Messages

由编译器自动生成的message类是不可变的,一旦一个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();

标准的Message方法

对于每个message或builder类也包含一些方法用于检查或操作整个消息,如:

·        isInitialized():检查是否所有的required字段已经设置了值;

·        toString():返回一个易于阅读的消息结果,对于调试来说非常有用;

·        mergeFrom(Message other): 将其它内部merger到当前的消息中,重写单一值域或者新增repeated域,仅用于builder。

·        clear():将所有域清空设置,仅用于builder。

解析及序列化

最终,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中查阅更全面的的接口。

写入消息

接下来先看如何来用protocol buffer类,对于地址薄应用首先需要将个人资料写入地址薄中。为了做到这些,需要创建protocol buffer类并将信息写入。程序设计如下,会先从一个文件读取AddressBook信息,通过用户手工输入一个Person的信息,交将其回写至 AddressBook文件中。代码示例如下,其中高亮部分是protobuf自动生成的代码。

import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.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.addPhone(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();
  }
}

读取消息

当然了,如果只有地址薄不能读取也是一件悲剧的事情,下面的代码示例就是从文件中读取该地址薄中的个人详细信息。

import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.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.getPersonList()) {
      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.getPhoneList()) {
        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);
  }
}

对Protocol Buffer进行扩展

有时会发现在发布完protocolbuffer代码后,需要对其进行扩展升级。如果想让新代码向后兼容,而且老代码能够向前兼容,此时需要遵循以下的规则。

·        不能改变已存在域的标识号;

·        不要任意添加或删除required修饰的域;

·        可以删除optional或repeated修饰的域;

·        可以新增optional或repeated修饰的域,但是必须使用新的标识号。

如果按照上述规约进行了升级,旧的代码将可以读取新的消息并将一些新的字段忽略掉。对于旧代码,被删除的optional域将会使用其默认值,删除 的repeated域将会被置空。新代码中也将能够透明地读取旧的消息,但是有一点需要明确,那就是新的optional域不能出现在旧消息中,可以通过 has方法进行明确检查,或者在.proto文件中为该字段提供一个默认值。如果一个optional元素没有明确的声明默认值的话,则会根据其类型取默 认值,如:字符串类型,取空串为默认值;布尔类型取false为其默认值;数字类型取0为其默认值。如果新增了一个repeated域,新代码将不能判断 其是否是空,老代码也不会设置其值,且它并没有has方法。

高级用法

Protocol Buffers目前已经能够提供的功能远超过了上述介绍的简单访问及序列化,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/java/index.html中发掘更高级的特性。

Protocol消息类提供的一个主要特性是反射,对于任何具体的消息类型在不需要写代码的情况下就可以迭代其中的域并操控其中的值。其有效的应用 场景即可将其它编码(XML、JSON)的消息转换成protocol消息。一个更高级的反射应用即可以发现同一类型消息的差异,或者是采用一系列正则表 达式来匹配一定的消息内容。充分发挥想象力,protocol buffer将能够解决更广范围的问题。其中反射是作为Message及Message.Builder的接口的一部分而提供的。

 

proto type --- java type

.proto类型

Java 类型

C++类型

备注

double

double

double

 

float

float

float

 

int32

int

int32

使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。

int64

long

int64

使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。

uint32

int[1]

uint32

Uses variable-length encoding.

uint64

long[1]

uint64

Uses variable-length encoding.

sint32

int

int32

使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。

sint64

long

int64

使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。

fixed32

int[1]

uint32

总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。

fixed64

long[1]

uint64

总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。

sfixed32

int

int32

总是4个字节。

sfixed64

long

int64

总是8个字节。

bool

boolean

bool

 

string

String

string

一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。

bytes

ByteString

string

可能包含任意顺序的字节数据。

 

 

分享到:
评论

相关推荐

    protoc.exe和protobuf-java-3.6.1集合

    在实际应用中,protobuf常用于服务端和客户端之间的通信数据格式,如RPC框架gRPC的基础就是protobuf,它使用protobuf定义接口和消息,protoc生成服务端和客户端代码,从而实现高效的远程调用。 总的来说,protoc....

    protobuf-java-2.5.0.jar

    4. **数据类型支持**:protobuf支持多种基础数据类型以及复杂的数据结构,如消息(message)、数组、枚举(enum)和可选/重复字段。 5. **反射API**:protobuf提供反射API,允许在运行时动态地处理protobuf消息,...

    Google Protobuf 编译 成 java 文件

    **Google Protobuf简介** Google Protocol Buffers...通过阅读提供的PDF文档,你将进一步了解Protobuf的Java生成代码细节、编码规则、基础用法以及开发者指南。这些文档将帮助你深入理解Protobuf的使用和最佳实践。

    protobuf-java-3.10.0.zip

    - **扩展性**:protobuf允许在不改变原有消息类型的基础上添加新的字段,增强了灵活性。 - **服务定义**:protobuf还可以用于定义RPC(远程过程调用)服务,通过gRPC框架提供高性能的微服务间通信。 protobuf在实际...

    java序列化之protobuf

    在实际开发中,protobuf常用于微服务之间的通信,例如gRPC框架就是基于protobuf构建的,它提供高性能、HTTP/2为基础的RPC框架。 总的来说,protobuf是Java序列化的一个强大工具,尤其在处理大量数据交换和跨平台...

    Unity3D 与 Java 基于 Protobuf 通信实现(客户端)

    本教程将深入探讨如何利用Protocol Buffers(Protobuf)在Unity3D客户端与Java服务器之间实现通信。Protocol Buffers是一种高效的数据序列化协议,由Google开发,它能够将结构化数据转化为二进制格式,便于网络传输...

    《protobuf》基础语法3

    - **编译器**:`protoc`是protobuf的编译工具,将.proto文件转换为不同语言(如C++、Java、Python)的源代码。 - **编码过程**:将结构化数据按照protobuf的规则转化为二进制格式,以便在网络中传输或存储。 - **...

    基于Protobuf动态解析在Java中的应用 包含例子程序

    protobuf的动态解析机制要求提供二进制内容的基础上,再提供对应类的Descriptor对象,在解析时通过DynamicMessage类的成员方法来获得对象结果。Descriptor对象可以通过protoc命令生成descriptor文件,进而得到的。 ...

    Springboot集成websocket+protobuf基本Demo,可实现后端与前端数据实时发送与接收.zip

    WebSocket是一种在客户端和服务器之间建立长连接的协议,它允许双方进行全双工通信,即...这个示例项目"Springboot集成websocket+protobuf基本Demo"提供了一个基础的实现,可以帮助开发者快速理解和实践这一技术组合。

    protobuf-3.1.x.zip

    - `src/`目录:源代码,包含C++、Java和Python等语言的protobuf库。 - `include/`目录:头文件,供不同编程语言的开发人员在项目中引用protobuf的API。 - `examples/`目录:示例代码,展示如何使用protobuf进行序列...

    Protobuf-master包

    **一、Protobuf 基础** 1. **数据序列化**:Protobuf 提供了一种将结构化数据序列化为二进制格式的方法,使得数据可以跨平台、跨语言传输和存储。序列化后的数据比 JSON 或 XML 更紧凑,减少了网络传输的数据量。 ...

    protoc-3.2.0-win32和protobuf-java-3.2.0.jar打包

    综上所述,这个压缩包提供了在Windows 32位系统上使用protobuf进行数据序列化的基础工具,用户需要结合自己的项目需求,编写.proto文件,使用protoc编译器生成相应的代码,并将protobuf-java-3.2.0.jar库引入到Java...

    ProtoBuf开发者指南 .docx

    7. Java基础 ProtoBuf在Java中的实现主要包括两个部分:编译器和API。编译器将.proto文件编译成Java代码,而API提供了操作ProtoBuf数据的接口。 8. Python基础 ProtoBuf在Python中的实现主要包括两个部分:编译器...

    protobuf-3.8.0.zip

    7. **protobuf与gRPC**:protobuf不仅用于数据序列化,还是gRPC(一个高性能、开源的RPC框架)的基础。在gRPC中,服务接口定义同样使用.proto文件,protobuf负责将消息转换为HTTP/2的请求和响应。 8. **版本兼容性*...

    Protobuf简单使用及其抓包分析

    总的来说,这个资料包提供了一个学习和实践Protobuf的基础环境,涵盖了从定义消息结构、编译`.proto`文件到编写Java代码进行数据交换的全过程,同时也包含了抓包分析,帮助理解Protobuf在网络传输中的实际应用。...

    Unity与Java进行ProtoBuf序列化工具

    1. **ProtoBuf协议**: 了解ProtoBuf的基本语法,包括消息定义、字段类型、选项等,这是编写.proto文件的基础。 2. **.proto文件**: 创建.proto文件,定义共享的数据结构,如消息类型、字段及其类型。例如: ```...

    基于netty和protobuf的聊天系统,客户端+服务器

    这个项目“基于netty和protobuf的聊天系统,客户端+服务器”就是这样一个实例,它展示了如何利用Java语言结合Netty框架和Protocol Buffers(protobuf)来搭建一个高性能、低延迟的聊天应用。 Netty是一个开源的异步...

    protobuf.zip

    总的来说,protobuf.zip文件提供了在Java环境中使用gRPC和protobuf的基础工具,包括protobuf编译器和gRPC Java插件,以及一个可以学习和实践的示例。通过这些工具,开发者能够轻松地构建高效、跨平台的RPC系统,简化...

    protobuf 中文操作手册

    `Protocol Buffer Basics - Java.pdf`介绍了protobuf在Java环境下的基础用法,包括如何创建消息实例、设置和获取字段值、以及与其他数据格式(如JSON)之间的转换。 6. **开发指南**: `Protocol Buffers ...

    Netty发送protoBuf格式数据

    首先,理解ProtoBuf的工作原理是基础。ProtoBuf定义了一种结构化的数据表示方式,通过.proto文件来描述数据结构。这个文件包含了消息类型、字段、字段类型以及它们的ID等信息。编译器会根据.proto文件生成对应语言...

Global site tag (gtag.js) - Google Analytics