probobuffer学习笔记

简单介绍

protobuf是谷歌推出的一种序列化协议,在认识protobuf之前,我们需要着重认识一下序列化,序列化和反序列化是什么?有什么用呢?

设想一种情景:现在在内存中有一个Java的对象,他在内存中,只要程序一结束或者电脑一断电,这个对象就消失了。现在需要将这个对象保存到物理硬盘上或者需要通过网络进行传输,那该怎么办呢?答案就是序列化,将一个对象序列化成二进制文件,在需要的时候再将它还原成Java对象,这个二进制文件不仅可以在物理硬盘上进行存储,还能在网络上进行传输。这就是序列化的作用,但是Java中的序列化有一些缺点,一是只能在Java中使用,不能跨语言;二是在进行序列化时,需要保存很多的class类型信息,所以序列化之后的二进制文件很大。

protobuf也是做序列化反序列化这个事情的,只不过,protobuf有许多的优点:跨语言,支持许多主流的言语:如Java、C++、Python……,还跨平台,Windows、Linux、Mac。另外一个重要的特性就是,序列化之后的二进制文件小,用在一些网络性能要求高的地方非常合适。

基本语法

syntax = "proto3"
option java_package = "com.ql.protobuftest.util";
option java_outer_classname = "MultSerializer";

message Teacher{
	required string name = 1;
	option int32 age = 2;
	repeated string number = 3;
	// 这是注释
	/*这也是注释*/
}

一、非message内容

  • syntax 用于指定protobuffer的语法版本,不加默认是2.0版本。这必须是文件第一个非空、非注释的行
  • option java_package 表示生成的序列化器的Java包
  • option java_outer_classname 用于指定生成的Java的类名
二、message部分

消息结构体跟Java的类很像,一个message就对应着Java中的一个类,每个字段对应了Java中的一个属性。在上面的示例中,Teacher就对一个Teacher类,有三个字段,name,age,number。标注 proto中的数据类型 字段名 = 唯一编号

1. 标注

标注是用来限制字段在进行设置时的要求

  • required:表示在设置消息的时候,这个属性是必须要的,而且只能设置一个值。在proto3语法中,已经没有这个标注了
  • option:表示在设置消息的时候,这个属性是可选的,可以设置,可以不设置,不设置会有默认值
  • repeated:表示可重复的,就是这个属性可以设置多个值(0个到多个),可类比于Java中的数组,但实际上在生成Java类的时候,用的是list。

注意:在proto3语法中,已经没有required和option了,只有singular和repeated,不写默认是singular。

2. protobuffer变量

protobuffer中内置了一些变量,可以和每种语言中变量对应起来,因为protobuffer跨语言,所以可以对应起来,例如:

proto变量Java类型C++类型
floatfloatfloat
doubledoubledouble
int32intint32
int64longint64
………………

太多久不一一列举了,可以参考:protobuffer官网参考文档
值得一提的是,protobuffer和Java一样,除了内置的基本类型之外,还支持枚举、自定义类型

3. 变量名

变量的命名规则和大多数编程语言的命名规则是一样的,数字、字母、下划线等

4. 属性顺序号

这个编号是唯一的,不能重复的,编号范围 1-536870911,19000-19999之间的数字不能使用(因为是保留数字)。在编译之后,这个数字会代替变量名,这也是protobuffer序列化之后体积更小的原因之一,如果是用json格式,那么name就真的需要存储一个name,但是使用protobuffer(像我上面那么写),name就只需要用一个数字代替。

1-15编号占用一个字节,16-2047占用两个字节,所以对于常用的的字段,应该为其保留1-15号序号。

5. 注释

proto中注释采用和Java类似的风格,双斜线(//)和双斜线加星号(/**/)都可以注释

高级语法

1. 使用自定义类型

可以使用自己定义的message作为变量的类型,例如

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}
2. 嵌套

可以在一个message内部嵌套定义额外的message,例如:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

如果想要在别的message内部使用message Result,可以使用_Parent_._Type_语法,例如:

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

另外,可以多层嵌套

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}
2. 枚举

如希望有一种类型,它的值只能是某些固定的值,那么枚举可能再合适不过了。一个枚举类型可以自定义一些值,在为这个类型的变量进行赋值时,只能在自定义的值中进行选择。例如:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  // 此处定义了一个枚举类型
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  // 此处使用了枚举类型
  Corpus corpus = 4;
}

注意:枚举的第一个选项值必须为0,因为:①必须得有一个0作为枚举类型变量的默认值;②得和proto2语法兼容,proto2语法中枚举第一个选项值必须为0

另外,在枚举中可以将相同的值分配给不同的选项,以表示这是两个相同的选项,只是别名,但是需要在申明,否则会编译不通过

message MyMessage1 {
  enum EnumAllowingAlias {
  	// 表是在本消息里可以使用别名
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}

枚举的常量值必须在32位整数的范围内,而且不推荐使用负值,因为效率很低。
枚举可以定义在一个message内部,只作为当前message内部使用。枚举还可以定义在message外部,当前proto文件内部所有的message都可以使用这个枚举。还可以使用语法_MessageType_._EnumType_在一个消息中声明的enum类型作为另一个消息中的字段类型。

同样,如果直接删除或者注释掉枚举里面的某些选项,那么会出现兼容性问题,所以推荐的做法也是使用reserved进行保留。例如:

enum Foo {
  // 保留枚举值,2,15,9到11,40到max
  reserved 2, 15, 9 to 11, 40 to max;
  // 保留枚举名 “FOO”,“BAR”
  reserved "FOO", "BAR";
}

注意,不能在同一个保留语句中混合字段名和字段号

4. map

如果想构建一个map,在proto中是可行的,语法为:map<key_type, value_type> map_field = N,例如:

map<string, Project> projects = 3;

注意:枚举不是一个有效的key,不能使用any作value,使用map还需要满足一些要求

  • map的字段名不能重复
  • 如果为map提供了一个key,但是没有为其提供value,那么value是依据语言而定的。 In C++, Java, and Python the default value for the type is serialized, while in other languages nothing is serialized.
  • Wire format ordering and map iteration ordering of map values is undefined, so you cannot rely on your map items being in a particular order.
  • When generating text format for a .proto, maps are sorted by key. Numeric keys are sorted numerically.
  • When parsing from the wire or when merging, if there are duplicate map keys the last key seen is used. When parsing a map from text format, parsing may fail if there are duplicate keys.
3. 扩展性

直接删除或者注释某个字段,会让之前在使用该字段的地方出现错误,也就是不向前兼容,所以不建议这么做,推荐的做法是用reserved进行保留。

message Foo {
  // 保留字段号2,15,9到11
  reserved 2, 15, 9 to 11;
  // 保留字段名"foo"、"bar"
  reserved "foo", "bar";
}

进行保留之后,更新消息时,新定义字段不能使用继续使用保留的字段号和字段名,但是可以将这个字段是存在的进行使用(不推荐)。注意,不能在同一个保留语句中混合字段名和字段号。

4. 默认值

在进行反序列化时,某个字段没有为其设置值,那么根据其类型会有默认值。

  • 对于string,默认值是空字符串
  • 对于bytes,默认值是空的bytes
  • 对于布尔值,默认值是false
  • 对于数值类型,默认值是0
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
  • 对于复合类型(也就是Java中的引用类型),如果没有设置值,默认值和具体的语言有关。
  • 重复的字段默认值为空

注意:如果一个基本类型变量的字段被设置为默认值,这个值将不会参与序列化。

自我提高

1. 如果proto文件太大怎么办?

java是单个类文件不能超过65k,如果proto协议文件过大则会导致失败,解决办法是在头部加上:

option java_multiple_files = true;

3. 属性顺序号为什么不能重复?

因为在序列化和反序列化的时候,消息的字段都是用这个属性顺序号进行代替的,如果重复了,字段区分将会出现问题,所以要求属性顺序号唯一不可重复。

4. protobuffer为什么效率高
  1. 在进行反序列化时,为什么能将一个二进制信息还原成一个Java对象?为什么能还原成一个Teacher实例,而不是Pig、Dog类型实例呢?这是因为序列化之后的信息中包含了class信息,所以可以反序列化还原。但是使用protobuffer,class信息就不会在序列化后的二进制信息中,而是在编译生成的Java工具类中,所以就节省了空间。
  2. 字段信息也不会使用实际的文字表示,而是使用了属性顺序号进行代替,例如一个字段"name",需要占用四个字节,但是如果使用属性顺序号"1"进行替代,则只需要一个字节。
  3. 动态的分配值的空间,例如在Java中,一个int类型的变量需要占用4个字节,无论这个变量实际是否用到了4个字节。但是protobuffer在进行序列化的时候,会根据变量的实际大小,动态的分配空间,所以也节省了空间。
5. 每个消息都去生成一个对应的类吗?如果有成千上万个消息呢?在真实开发中,protobuffer是怎么使用的?

这个占时还不知道,因为网上关于proto的资料还比较少,我上面的东西,大部分都是从proto官网上扣下来的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值