简介
Protocol Buffer是google出品的一种对象序列化的形式,它的体积小传输快,深得大家的青睐。protobuf是一种平台无关和语言无关的协定,通过protobuf的定义文件,能够轻松的将其转换成多种语言的实现,十分不便。
明天将会给大家介绍一下,protobuf的根本应用和同java联合的具体案例。
为什么应用protobuf
咱们晓得数据在网络传输中是以二进制进行的,个别咱们应用字节byte来示意, 一个byte是8bits,如果要在网络上中传输对象,个别须要将对象序列化,序列化的目标就是将对象转换成byte数组在网络中传输,当接管方接管到byte数组之后,再对byte数组进行反序列化,最终转换成java中的对象。
那么将java对象序列化可能会有如下几种办法:
- 应用JDK自带的对象序列化,然而JDK自带的序列化自身存在一些问题,并且这种序列化伎俩只适宜在java程序之间进行传输,如果是非java程序,比方PHP或者GO,那么序列化就不通用了。
- 你还能够自定义序列化协定,这种形式的灵便水平比拟高,然而不够通用,并且实现起来也比较复杂,很可能呈现意想不到的问题。
- 将数据转换成为XML或者JSON进行传输。XML和JSON的益处在于他们都有能够辨别对象的起始符号,通过判断这些符号的地位就能够读取到残缺的对象。然而不论是XML还是JSON的毛病都是转换成的数据比拟大。在反序列化的时候对资源的耗费也比拟多。
所以咱们须要一种新的序列化的办法,这就是protobuf,它是一种灵便、高效、自动化的解决方案。
通过编写一个.proto的数据结构定义文件,而后调用protobuf的编译器,就会生成对应的类,该类以高效的二进制格局实现protobuf数据的自动编码和解析。 生成的类为定义文件中的数据字段提供了getter和setter办法,并提供了读写的解决细节。 重要的是,protobuf能够向前兼容,也就是说老的二进制代码也能够应用最新的协定进行读取。
定义.proto文件
.proto文件中定义的是你将要序列化的音讯对象。咱们来一个最根本的student.proto文件,这个文件定义了student这个对象中最根本的属性。
先看一个比较简单的.proto文件:
syntax = "proto3";package com.flydean;option java_multiple_files = true;option java_package = "com.flydean.tutorial.protos";option java_outer_classname = "StudentListProtos";message Student { optional string name = 1; optional int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; } message PhoneNumber { optional string number = 1; optional PhoneType type = 2; } repeated PhoneNumber phones = 4;}message StudentList { repeated Student student = 1;}
第一行定义的是protobuf中应用的syntax协定,默认状况下是proto2,因为目前最新的协定是proto3,所以这里咱们应用proto3作为例子。
而后咱们定义了所在的package,这个package是指编译的时候生成文件的包。这是一个命名空间,尽管咱们在前面定义了java_package,然而为了和非java语言中的协定相冲突,所以定义package还是十分有必要的。
而后是三个专门给java程序应用的option。java_multiple_files, java_package, 和 java_outer_classname.
其中java_multiple_files指编译过后java文件的个数,如果是true,那么将会一个java对象一个类,如果是false,那么定义的java对象将会被蕴含在同一个文件中。
java_package指定生成的类应该应用的Java包名称。 如果没有明确的指定,则会应用之前定义的package的值。
java_outer_classname选项定义将示意此文件的包装类的类名。 如果没有给java_outer_classname赋值,它将通过将文件名转换为大写驼峰来生成。 例如,默认状况下,“student.proto”将应用"Student"作为包装类名称。
接下来的局部是音讯的定义,对于简略类型来说能够应用bool, int32, float, double, 和 string来定义字段的类型。
上例中咱们还应用了简单的组合属性,和嵌套类型。还定义了一个枚举类。
下面咱们为每个属性值调配了ID,这个ID是二进制编码中应用的惟一“标签”。因为在protobuf中标记数字1-15比16以上的标记数字占用的字节空间要更少,因而作为一种优化,通常将1-15这些标记用于罕用或反复的元素,而将标记16和更高的标记用于不太罕用的可选元素。
而后再来看看字段的修饰符,有三个修饰符别离是optional,repeated和required。
optional示意该字段是可选的,能够设置也能够不设置,如果没有设置,则会使应用默认值,对于简略类型来说,咱们能够自定义默认值,如果不自定义,就会应用零碎的默认值。对于零碎的默认值来说,数字为0,字符串为空字符串,布尔值为false。
repeated示意该字段是能够反复的,这种反复实际上就是一种数组的构造。
required示意该字段是必须的,如果该字段没有值,那么该字段将会被认为是没有初始化,尝试构建未初始化的音讯将抛出 RuntimeException,解析未初始化的音讯将抛出 IOException。
留神,在Proto3中不反对required字段。
编译协定文件
定义好proto文件之后,就能够应用protoc命令对其进行编译了。
protoc是protobuf提供的编译器,个别状况下,能够从github的release库中间接下载即可。如果你不想间接下载,或者官网提供的库中并没有你须要的版本,则能够应用源代码间接进行编译。
protoc的应用的命令如下:
protoc --experimental_allow_proto3_optional -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/student.proto
如果编译proto3,则须要增加--experimental_allow_proto3_optional选项。
咱们运行一下下面的代码。会发现在com.flydean.tutorial.protos包外面生成了5个文件。别离是:
Student.java StudentList.java StudentListOrBuilder.java StudentListProtos.java StudentOrBuilder.java
其中StudentListOrBuilder和StudentOrBuilder是两个接口,Student和StudentList是这两个类的实现。
详解生成的文件
在proto文件中,咱们次要定义了两个类Student和StudentList, 他们中定义了一个外部类Builder,以Student为例,看下这个两个类的定义:
public final class Student extends com.google.protobuf.GeneratedMessageV3 implements StudentOrBuilder public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements com.flydean.tutorial.protos.StudentOrBuilder
能够看到他们实现的接口都是一样的,示意他们可能提供了雷同的性能。实际上Builder是对音讯的一个封装器,所有对Student的操作都能够由Builder来实现。
对于Student中的字段来说,Student类只有这些字段的get办法,而Builder中同时有get和set办法。
对于Student来说,对于字段的办法有:
// 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);
对于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 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();
多出的两个办法是set和clear办法。clear是清空字段的内容,让其变回初始状态。
咱们还定义了一个枚举类PhoneType:
public enum PhoneType implements com.google.protobuf.ProtocolMessageEnum
这个类的实现和一般的枚举类没太大区别。
Builders 和 Messages
如上一节所示,Message对应的类只有get和has办法,所以它是不能够变的,音讯对象一旦被结构,就不能被批改。要构建音讯,必须首先构建一个构建器,将要设置的任何字段设置为你抉择的值,而后调用构建器的 build()办法。
每次调用Builder的办法都会返回一个新的Builder,当然这个返回的Builder和原来的Builder是同一个,返回Builder只是为了不便进行代码的连写。
上面的代码是如何创立一个Student实例:
Student xiaoming = Student.newBuilder() .setId(1234) .setName("小明") .setEmail("flydean@163.com") .addPhones( Student.PhoneNumber.newBuilder() .setNumber("010-1234567") .setType(Student.PhoneType.HOME)) .build();
Student中提供了一些罕用的办法,如isInitialized()检测是否所有必须的字段都设置结束。toString()将对象转换成为字符串。应用它的Builder还能够调用clear()用来革除已设置的状态,mergeFrom(Message other)用来对对象进行合并。
序列化和反序列化
生成的对象中提供了序列化和反序列化办法,咱们只须要在须要的时候对其进行调用即可:
- byte[] toByteArray();: 序列化音讯并返回一个蕴含其原始字节的字节数组。
- static Person parseFrom(byte[] data);: 从给定的字节数组中解析一条音讯。
- void writeTo(OutputStream output);: 序列化音讯并将其写入 OutputStream.
- static Person parseFrom(InputStream input);: 从一个音讯中读取并解析音讯 InputStream.
通过应用下面的办法,能够很不便的将对象进行序列化和反序列化。
协定扩大
咱们在定义好proto之后,如果后续还心愿对其进行批改,那么咱们心愿新的协定对历史数据是兼容的。那么咱们须要思考上面几点:
- 不能更改现有字段的ID编号。
- 不能增加和删除任何必填字段。
- 能够 删除可选或反复的字段。
- 能够 增加新的可选字段或反复字段,但您必须应用新的ID编号。
总结
好了,protocol buf的根本用法就介绍到这里,下一篇文章咱们会更加具体的介绍proto协定的具体内容,敬请期待。
本文的例子能够参考:learn-java-base-9-to-20
本文已收录于 http://www.flydean.com/01-protocolbuf-guide/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!