什么是ProtoBuffers
ProtoBuf(Protocol Buffers)是一种跨平台、语言无关、可扩展的序列化结构数据的方法,可用于网络数据交换及存储。一般简称pb
一旦定义了要处理的数据的数据结构之后,就可以利用ProtoBuf的代码生成工具生成相关的代码。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言(proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#)或从各种不同流中对你的结构化数据轻松读写。
为什么是 ProtoBuffers
ProtoBuf 现在是 Google 用于数据交换和存储的通用语言。谷歌代码树中定义了 48162 种不同的消息类型,包括 12183 个 .proto 文件。它们既用于 RPC 系统,也用于在各种存储系统中持久存储数据。
- 性能数据(和json对比)
[图片上传失败...(image-73a327-1663947957237)]
[图片上传失败...(image-9bfaf5-1663947957237)]
序列化时间效率对比:
数据格式 | 1000条数据 | 5000条数据 |
---|---|---|
Protobuf | 195ms | 647ms |
Json | 515ms | 2293ms |
序列化空间效率对比:
数据格式 | 5000条数据 |
---|---|
Protobuf | 22MB |
Json | 29MB |
优点
- 效率高
从序列化后的数据体积角度,与XML、JSON这类文本协议相比,ProtoBuf通过T-(L)-V(TAG-LENGTH-VALUE)方式编码,不需要", {, }, :等分隔符来结构化信息,同时在编码层面使用varint压缩,所以描述同样的信息,ProtoBuf序列化后的体积要小很多,在网络中传输消耗的网络流量更少,进而对于网络资源紧张、性能要求非常高的场景,ProtoBuf协议是不错的选择。
从序列化/反序列化速度角度,与XML、JSON相比,ProtoBuf序列化/反序列化的速度更快,比XML要快20-100倍。 - 支持跨平台、多语言
ProtoBuf是平台无关的,无论是Android与PC,还是C#与Java都可以利用ProtoBuf进行无障碍通讯。 - 扩展性、兼容性好
具有向后兼容的特性,更新数据结构以后,老版本依旧可以兼容,这也是ProtoBuf诞生之初被寄予解决的问题。因为编译器对不识别的新增字段会跳过不处理。
proto3支持C++, Java, Python, Go, Ruby, Objective-C, C#。
- 使用简单
ProtoBuf 提供了一套编译工具,可以自动生成序列化、反序列化的样板代码,这样开发者只要关注业务数据idl,简化了编码解码工作以及多语言交互的复杂度。
缺点
- 可读性差,缺乏自描述
XML,JSON是自描述的,而ProtoBuf则不是。
ProtoBuf是二进制协议,编码后的数据可读性差,如果没有idl文件,就无法理解二进制数据流,对调试不友好。
如何使用pb
- 在项目的根build.gradle配置如下
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.19'
}
或
plugins {
...
id 'com.google.protobuf' version '0.8.19' apply false
}
- 在modele下build.gradle中配置如下:
apply plugin: 'com.google.protobuf'
android {
sourceSets {
main {
// 定义proto文件目录
proto {
srcDir 'src/main/proto'
include '**/*.proto'
}
}
}
}
dependencies {
implementation 'com.google.protobuf:protobuf-java:3.17.2'
}
protobuf {
//配置protoc编译器
protoc {
// 不支持 mac m1 arg使用x86_64
if (osdetector.os == "osx") {
//区分平台
artifact = 'com.google.protobuf:protoc:3.17.2:osx-x86_64'
} else {
artifact = 'com.google.protobuf:protoc:3.17.2'
}
}
//这里配置生成目录,编译后会在build的目录下生成对应的java文件
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.builtins {
java {}
}
}
}
}
或
plugins {
id 'com.google.protobuf'
}
android {
sourceSets {
main {
// 定义proto文件目录
proto {
srcDir 'src/main/proto'
include '**/*.proto'
}
}
}
}
dependencies {
implementation 'com.google.protobuf:protobuf-java:3.17.2'
}
protobuf {
//配置protoc编译器
protoc {
// 不支持 mac m1 arg使用x86_64
if (osdetector.os == "osx") {
//区分平台
artifact = 'com.google.protobuf:protoc:3.17.2:osx-x86_64'
} else {
artifact = 'com.google.protobuf:protoc:3.17.2'
}
}
//这里配置生成目录,编译后会在build的目录下生成对应的java文件
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.builtins {
java {}
}
}
}
}
- 在model的main文件下建proto目录,并在下面建.proto文件
Person.proto
//pb版本
syntax = "proto3";
//存放位置
option java_package = "com.mysiga.netsocket";
//输出类名
option java_outer_classname = "Person";
//引入类
import "Adress.proto";
//定义类
message _Person{
// 定义字段为name的字段,数据为字符串
string name = 1;
int32 id = 2;
string email = 3;
bool isCheck = 4;
// 枚举
enum _PhoneType{
MOBILE=0;
HOME=1;
WORK=2;
}
// 内部类
message _PhoneNumber{
string number=1;
_PhoneType type=2;
}
string card=5;
// 集合
repeated _PhoneNumber phone=6;
_Adress adress=7;
}
Adress.proto
syntax="proto3";
option java_package="com.mysiga.netsocket";
option java_outer_classname="Adress";
message _Adress{
string address=1;
}
-
[Make Project]编译下项目
会在app/build/intermediates/javac/debug/包名/下生成相应class文件
- 项目中调用
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val phoneNumber=Person._Person._PhoneNumber.newBuilder().setNumber("1883883")
val personBuild=Person._Person.newBuilder().setCard("汽车")
.setEmail("121431@qq.com").setIsCheck(true).addPhone(phoneNumber).setName("Wilson")
val person=personBuild.build()
Log.i("Wilson","name ${person}")
// 序列化
val bytes=person.toByteArray()
// 反序列化
try {
val person1=Person._Person.parseFrom(bytes)
Log.i("Wilson","name:${person1}")
Log.i("Wilson","card:${person1.card}")
} catch (e: InvalidProtocolBufferException) {
e.printStackTrace()
}
}
}
总结
对于数据交互频繁且数据量大,建议使用pb,对于游戏或在线教育白板业务场景下基本都是用pb。只是可读性差点。如果数据量不大可用Json可读性高。以上就是简单实用。复杂实用还要深研究下官方文档了。
扩展资料
- protocol-buffers官方文档
- 附语言类型对照表
.proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type |
---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | |
float | float | float | float | float32 | Float | float | float | |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
uint32 | 使用变长编码 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
uint64 | 使用变长编码 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 | int64 | long | int/long | int64 | Bignum | long | integer/string |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根据需要) | uint | integer |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
sfixed32 | 总是4个字节 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int | integer |
sfixed64 | 总是8个字节 | int64 | long | int/long | int64 | Bignum | long | integer/string |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | string | String | str/unicode | string | String (UTF-8) | string | string |
bytes | 可能包含任意顺序的字节数据。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |