Protobuf的那些事

当大多数公司还在纠结于如何更好提高MobileAPI的性能时,有的公司已经开始抛弃http + json,开始走向tcp + protobuf的路线了。

那么什么是protobuf呢protobuf是一种基于二进制的协议,它能够非常快速高效的序列化数据,考虑之前的xml,但是它的体积要比xml小的多得多。当然使用它还有其他非常之多的理由。我们考虑java的情况,传统的序列化方法就是实现Serialization,不过它带来非常之多的烦恼,比如版本兼容,这会带来非常之大的测试压力,且它的处理完的数据并不能跨语言的分享啊,python or c++都不能够使用,这是很头疼的。除此之外,我们序列化数据的时候我们会使用一些ad-hoc way,比如会把数据都放在一个字符串里面,考虑有四个整形数据的例子:"12:3:-23:67",它显然是足够简单的,但是却需要手写一些解析,编码代码,并且,还有一些运行时损耗。再后者,我们可能会想到使用xml,显然它是不太好的,不然也就没有json了。

综合以上,便是protobuf诞生的理由,现在我们要开始正式学习它了。


在使用之前,我们需要安装它的库,编译器啥的,转到github :https://github.com/google/protobuf,按照readme里面下载安装就行了,之后安装Java的版本,如果一切都正常的话 在终端里面输入protoc --version会看到当前protoc编译器的版本号。


那再用java来使用protobuf的时候,我们需要三个步骤:

1:定义.proto文件,用来定义你的数据结构

2:使用protoc编译器进行编译,导出java代码

3:使用java api进行数据的读写


我这里考虑创建一个 AddressBook的例子

在这个类中我们存放联系簿,练习簿中有你记录的联系人以及他的个人信息,包括手机号码,姓名啥的

我们先粗略的看下proto文件内容:






是不是似曾相识的感觉,他和c++,java代码还是很像的。我们逐行分析代码

第一行是指定版本号,如果没有,大概编译器会报如下错误:

[libprotobuf WARNING google/protobuf/compiler/parser.cc:547] No syntax specified for the proto file. Please use 'syntax = "proto2";' or 'syntax = "proto3";' to specify a syntax version. (Defaulted to proto2 syntax.

注意,它必须放在第一行哦,在下面是包名,他是用来防止Protocol Buffers name space里的名字冲突,所以还是不省略为好,再往下是生成java文件后的包名,很显然,第五行的便是类名了

如果你真的懒得写这些的话 关于他们缺省值你可以参考官网给出的解释:


再往下,你可以看到很多关于message的定义,他是用来存放数据域的,数据域有自己的类型,protobuf支持的类型有bool, int32, float, double, and string,包括自定义类型(也为message)等。比如Person有自己的名字,id,email,和联系电话,观之name,是string类型的,前面还有 required修饰符,表示这个域必须有,后面还有 = 1,这个符号,这是用来在二进制编码里面唯一确认当前的域的,值得注意的是1-15要求更少得存储空间去存储这个标记数字,也就是说,如果你某些域用的很频繁,应该首先分配这些数字,这也是作为优化的一种方式。

往下面读,可以看到email是optional,也就是可选的,这个域可有可无,如果没有设置的话,系统会自动填充一个合理地值,比如对于bool 你可以是false,字符串给定一个空值,所以,如果你对编译器没有信心,你可以自己给定一个默认的值,考虑第二十行的做法

对于PhoneType,他是一个枚举类型,枚举类型里面也要给定标记数字(Tag number)

通常情况下,一个人不可能只拥有一个号码,所以phone应该是一个集合,这个集合可以是变化的,可为空,你可以想象为一个动态变化的数组,所以它的修饰词是repeated

总结下:

一个域由以下部分组成:

修饰词 类型  域名  = 标记值

到目前为止,所有的基础内容已经介绍完,然后我们保存下文件,后缀名为.proto


切换到命令行模式:输入 

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

-I指定源目的文件夹,一般是你的应用源码位置

--java_out 是生成代码的目的地 一般和src dir相同

后面一个参数是proto文件的位置


具体的例子:

protoc -I=./ --java_out=./ ./first.proto


便可以在你指定的目标文件夹下面看到生成的Java文件了 :)


The Protocol Buffer API

我们看下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 phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);

同时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();

可以看到,Builder比Person有更“强的”方法。 每个 clear方法用于重置域到它的初始状态

重复域有count方法,用于返回这个域的大小


Enums and Nested Classes:

在Person中,我们定义了一个枚举类型,下面是枚举类型的Java代码:

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

Builders vs. Messages:

builder使用方式:

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("[email protected]")
    .addPhone(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.HOME))
    .build();



Message使用方式:

WRITE:

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();
  }
}

READ:

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);
  }
}

Extending a Protocol Buffer:

如果以后你想升级Proto文件定义,请遵守一下条款:

1:不可以修改现有域的任何标识数字(tag number)

2:不可以添加删除任何required域

3:你可以删除optional,repeated域

4:你可以添加optional repeated域,但是你必须要用不同的tag number(之前删除域的tag number也不能使用!)

protobuf github 包下载: http://download.csdn.net/detail/u013022222/9405304

点我

转载请注明出处:

http://blog.csdn.net/u013022222/article/details/50521835

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章