Protobuf是什么
Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。protobuf平台无关,语言无关,可扩展,提供了友好的动态库,使用简单,解析速度快,比对应的XML快约20-100倍,序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。
Protobuf安装
下载地址:https://github.com/google/protobuf
我们下载release版本的protobuf,首先是要下载编译器,编译器是由c++写成的,在源码的src目录下,这里我们下载预编译好的二进制可运行文件,在linux上的同学可以选择自己编译。
Protobuf 的使用
1. 创建一个.proto文件,定义好消息体
2. 用protobuf编译器编译生成java文件
3. import生成的java文件到需要使用的类中,就可以使用protobuf了
.proto文件
标准消息类型
proto类型 | java类型 | remark |
double | double | |
float | float | |
int32 | int | 使用可变长度编码。 负数无效 - 如果您的字段可能具有负值,请改用sint32。 |
int64 | long | 使用可变长度编码。 负数无效 - 如果您的字段可能具有负值,请改用sint64。 |
uint32 | int | 使用可变长度编码。 |
uint64 | long | 使用可变长度编码。 |
sint32 | int | 使用可变长度编码。 有符号的int值。 这些比常规的int32更有效地编码负数。 |
sint64 | long | 使用可变长度编码。 有符号的int值。 这些比常规的int64更有效地编码负数。 |
fixed32 | int | 四个字节。 如果值通常大于2^28,则比uint32效率更高。 |
fixed64 | long | 八个字节。 如果值通常大于2^56,则会比uint64更高效。 |
sfixed32 | int | 四字节 |
sfixed64 | int | 八字节 |
bool | boolean | |
string | String | 字符串必须始终包含UTF-8编码或7位ASCII文本。 |
bytes | ByteString | 可能包含任何字节序列。 |
默认值
strings,默认值为空白字符
bytes,默认值是空白字节
数字型,默认值是0
枚举值,默认值是枚举值里定义的第一个值,且必须为0
关键字
syntax - protobuf编译版本 - syntax="proto3";
package - 包名 - package proto;
option - 选项,可以生命导出包名类名等
- 常用选项 java_package 要用于生成的类包 - java_package = "com.example.foo";
java_multiple_files 表明不同顶级消息、枚举、服务分开java文件 - java_multiple_files = true;
java_outer_classname 要生成的类的类名 - java_outer_classname = "UserProto";
optimize_for 有三种选项(SPEED,CODE_SIZE,LITE_RUNTIME),影响这java和c++的代码生成器
1) SPEED - 默认,编译器将生成序列化,解析和执行消息类型的其他常见操作的代码。这个代码是高度优化的。
2) CODE_SIZE - 编译器将生成最小的类,依靠共享的基于反射的代码来实现序列化,解析和其他各种操作。生成的代码因此会比使用SPEED的代 码小得多,但操作会变慢。
3) LITE_RUNTIME - 编译器生成代码取决于运行时库,这样会使代码小很多,但是省略了描述符和反射等模块
cc_enable_arenas c++相关的配置,生成c++代码开启arena allocation
objc_class_prefix 设置所有Objective-C生成的类和从这个.proto枚举的Objective-C类前缀。
deprecated 如果设置为true,表示该字段被弃用,不应被新代码使用。 int32 old_field = 6 [deprecated=true];
map - key-value 类型 - map
enum - 枚举类型 - enum enum_field{...}
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;
}
repeated - 重复,重复的类似于java的List
message User{
int32 id = 1;
repeated string phone = 2;
}
reserved - 保留字段,如果您通过完全删除某个字段或将其注释掉来更新消息类型,那么未来的用户可以在对该类型进行自己的更新时重新使用该标签号。这可能会导致严重的问题,如果他们稍后加载相同的旧版本.proto,包括数据损坏,隐私错误,等等。确保这种情况不会发生的一种方法是指定删除字段的字段标记(和/或名称,这也可能会导致JSON序列化问题)reserved。如果有将来的用户尝试使用这些字段标识符,协议缓冲区编译器将会报错。
import - 引入.proto文件
一个完整的.proto文件示例
syntax = "proto3"; // protobuf 编译版本
package proto; // 包名
option java_package = "cn.com.suboo.study.beelt.proto"; // 生成的java包的名称
option java_outer_classname = "UserProtos"; // 生成的java类的类名
// 消息体
message User{
int32 id = 1;
string name = 2;
int32 age = 3;
// 枚举
enum Sex{
male = 0;
famale = 1;
}
Sex sex = 4;
enum phoneType {
mobile = 0;
home = 1;
work = 2;
}
message Phone {
phoneType type = 1;
string phoneNum = 2;
}
// 重复
repeated Phone phones= 5;
// map类型
map isClosed = 6;
}
生成java类
写好.proto文件之后,执行protobuf的编译器生成java文件
protoc --proto_path = IMPORT_PATH --cpp_out = DST_DIR --java_out = DST_DIR --python_out = DST_DIR --go_out = DST_DIR --ruby_out = DST_DIR --javanano_out = DST_DIR --objc_out = DST_DIR --csharp_out = DST_DIR path / to /文件 .proto
参数:
IMPORT_PATH - 指定.proto在解析import指令时查找文件的路径,如果省略则使用当前目录, 如果指定多个导入目录,则通过多个 --proto_path,proto按照顺序查找。简短形式 -I。
xxx_out - 提供一个导出形式,生成java或者c++形式的代码 如果是导出java 则 --java_out=xxxxxxxxx 如果,输出文件名结尾是.zip或者.jar结尾的,则导出一个到一个压缩文件里
参数必须提供一个或者多个.proto文件作为输入,.proto可以指定多个文件。
protobuf java的使用
每一个生成的java文件都有一个Builder类,使用的见着者模式,用建造者模式构建该类的实例。
生成的java文件字段声明部分,可见每个字段都有一个getter setter对,并且还有一个clear方法
// 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 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 value);
public Builder clearPhones();
public static enum PhoneType {
MOBILE(0, 0),
HOME(1, 1),
WORK(2, 2),
;
...
}
UserProtos.User user = UserProtos.User.newBuilder()
.setId(1)
.setAge(10)
.setSex(UserProtos.User.Sex.famale)
.setName("zhangsan")
.addPhones(0,
UserProtos.User.Phone.newBuilder()
.setType(UserProtos.User.phoneType.mobile)
.setPhoneNum("13313331333").build())
.build();
每个消息和构建器类还包含许多其他方法,可让您检查或处理整个消息,其中包括:
isInitialized():检查是否所有必填字段都已设置。
toString():返回一个人类可读的消息表示,对调试特别有用。
mergeFrom(Message other):(仅限构建器)将内容other合并到此消息中,覆盖奇异标量字段,合并复合字段以及连接重复字段。
clear():(仅限构建器)将所有字段清除回空状态。
接续和序列化
每个protobuf都有把消息写成protocal buffer的二进制消息类型的方法,包括:
byte[] toByteArray();:序列化消息并返回包含其原始字节的字节数组。
static Person parseFrom(byte[] data);:解析来自给定字节数组的消息。
void writeTo(OutputStream output);:序列化消息并将其写入到OutputStream。
static Person parseFrom(InputStream input);:读取并解析来自某个消息的消息InputStream。
写消息
@Test
public void writeToFile() {
UserProtos.User user = UserProtos.User.newBuilder()
.setId(1)
.setAge(10)
.setSex(UserProtos.User.Sex.famale)
.setName("zhangsan")
.addPhones(0,
UserProtos.User.Phone.newBuilder()
.setType(UserProtos.User.phoneType.mobile)
.setPhoneNum("13313331333").build())
.build();
try {
OutputStream outputStream = new FileOutputStream(
new File("D:/StudySpace/output/output1.txt")
);
user.writeTo(outputStream);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void readFromFile() {
File file = new File("D:/StudySpace/output/output1.txt");
UserProtos.User user = null;
try {
InputStream is = new FileInputStream(file);
user = UserProtos.User.parseFrom(is);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(user.getId());
System.out.println(user.getAge());
System.out.println(user.getName());
System.out.println(user.getSex());
for (UserProtos.User.Phone phone : user.getPhonesList()) {
System.out.println(phone.getType() + phone.getPhoneNum());
}
}