Protobuf学习笔记

Protobuf学习笔记

Protocol buffers是什么?

首先了解一下Protocol Buffers(简称ProtoBuf)是什么?官网对它的定义如下:

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler.

上述定义描述了Protocol Buffers的全部优点:语言无法,平台无关,可扩展,用于序列化结构化数据。在官方定义之外我认为protobuf一种通用结构化组织数据描述语言,拥有一整套的简单语法规则,内置的类型系统等等。因此在学习过程中,可以把它当作一门简单的语言来看待。Protocol Buffers定义了描述文件的结构,而描述文件结构的语法检查,类型检查则需要protoc来处理,这时protoc就是一个编译器,在编译过程中不但检查语法还负责生成指定格式的目标文件。
Protocol Buffers为以.proto文件生成的各种对象(不限语言)的提供一致的序列化手段,保证数据最终持久化后的格式一致。而需要序列化的数据,由用户根据.proto文件提供的类,创建相应的对象。

Protobuf的描述文件 proto

在使用Protobuf时,定义描述文件是最重要的事情。Protobuf描述文件格式如下:

/**
 * Created by IntelliJ IDEA.
 * User: huxing([email protected])
 * Date: 12-5-22
 * Time: 下午2:22
 */
package org.colorfuldays.ssm.domain.protobuf;

option java_package = "org.colorfuldays.ssm.domain.protobuf";

message User{
    optional int64 id = 1;
    optional string name = 2;
    optional string password = 3;
}

message book{

    optional int64 id = 1;
    optional string name = 2;
    optional string isbn = 3;
    repeated User author = 4;
    optional int64 publish_date = 5;
}
  • proto文件结构
    从上面的代码可以看出,proto文件的结构非常简单。
    package java_package 定义生成的java类的package名称。
    message定义一种类型,类型以Java中的一个class,book是类名,在生成Java代码时,就是使用这个类名。
    接下来是该类型中包含的字段,字段包括 [限定符 类型 字段名称 = tag [default = 默认值]]
  • protobuf支持的类型
    protobuf支持的默认类型如下
    .proto Type Notes C++ Type Java Type
    double   double double
    float   float float
    int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32 int
    int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long
    uint32 Uses variable-length encoding. uint32 int1
    uint64 Uses variable-length encoding. uint64 long1
    sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int
    sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64 long
    fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int1
    fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long1
    sfixed32 Always four bytes. int32 int
    sfixed64 Always eight bytes. int64 long
    bool   bool boolean
    string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String
    bytes May contain any arbitrary sequence of bytes. string ByteString

除此之外,还支持枚举,自定义类型等。使用方式见上面代码示例。更详细的介绍见官方文档
protobuf的自定义类型,支持对象组合方式,在CPP中使用较少,在Java中广泛采用,但是目前我们需要序列化的对象是瘦模型,用不上这种特性。

  • protobuf的限定符
  • required 必填项,如果该项不设值,在序列化时会抛出RuntimeException,推荐不使用该字段,该字段一旦使用,无法更改。不利于以来的扩展
  • optional 不存在时使用默认值,默认值可自定义。系统默认值如下:int = 0,bool = false,string=”"
  • repeated 类似于Java中的List,在实际生成Java类时,也是生成一个List来表示
  • tag说明
    tag 的主要作用是用来标识每一个field,序列化时会使用tag,取值为数字。如上面的id = 1,其中的1,即为tag。
    在protobuf序列化encode时标识每一个field,0-15比16-少一个byte,优化时可以使用到。
  • default值
    proto文件中定义的默认值在处理版本兼容时非常有用,下面会做相关介绍。
  • guide style
    Protocol Buffers官方提供了一个Guide Style。其中介绍了一些定义proto的优秀实践。一句话总结就是名称时,message名称以驼峰方式定义,字段名则需要以如author_name方式定义。字段名在生成Java代码时,会自动转换为符合Java风格的命名方式。

如何在项目中组织proto文件,暂时能想到下面三种方式:
1、一个项目中所有的class都在一个*.proto中定义?
2、每个class作为单独的文件?
3、按模块划分.proto文件
目前还没有确认哪种方式更佳。

Protobuf 生成的对象及序列化

Protobuf 生成的Java对象是immutable的,生成后无法修改,生成的Java对象以Builder方式构建,在调用Builder时为field设值。

  • 序列化方法
    Java接口提供的序列化相关方法如下:
    byte[] toByteArray();: serializes the message and returns a byte array containing its raw bytes.
    static Person parseFrom(byte[] data);: parses a message from the given byte array.
    void writeTo(OutputStream output);: serializes the message and writes it to an OutputStream.
    static Person parseFrom(InputStream input);: reads and parses a message from an InputStream.

    在处理序列化及反序列化对象时,发现难于对序列化方法的抽象,因为上述几个方法都不是public的。因此在序列化对象时,需要针对每一个Java对象实现其特定的序列化类。

扩展问题

Protobuf提供了极佳的扩展性,在扩展时,必须满足下面的要求:

  • you must not change the tag numbers of any existing fields.
  • you must not add or delete any required fields.
  • you may delete optional or repeated fields.
  • you may add new optional or repeated fields but you must use fresh tag numbers (i.e. tag numbers that were never used in this protocol buffer, not even by deleted fields).

在扩展之后可以实现下面的世界通
旧的系统可以读新的数据,但是无法读取到新添加的内容
新的系统可以读旧的数据,读不到的新加元素使用默认值

  旧数据 新数据
旧系统 不变 忽略新添加的字段
新系统 不存在的字段取默认值,删除的字段取默认值, 无变化
缺点:

Protobuf无疑是一个非常优秀的结构化数据序列化协议,现在我们来说一说在Java环境下,它的一些缺点:
1、不支持大数据集的处理,少于1M
2、不支持Date,Map这些Java内建的对象

更多详细信息请参照官方文件https://developers.google.com/protocol-buffers/docs


你可能感兴趣的:(Protobuf学习笔记)