probuffer java_Protocol Buffer的使用

Probotbuf简介

在网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,这两种技术常被用于数据的结构化呈现和序列化。我们可以从两个方面来看JSON 和 XML与protobuf的异同:一个是数据结构化,一个是数据序列化。这里的数据结构化主要面向开发或业务层面,数据序列化面向通信或存储层面,当然数据序列化也需要“结构”和“格式”,所以这两者之间的区别主要在于面向领域和场景不同,一般要求和侧重点也会有所不同。数据结构化侧重人类可读性甚至有时会强调语义表达能力,而数据序列化侧重效率和压缩。

JSON、XML 同样也可以直接被用来数据序列化,实际上很多时候它们也是这么被使用的,例如直接采用 JSON、XML 进行网络通信传输,此时 JSON、XML 就成了一种序列化格式,它发挥了数据序列化的能力。但是经常这么被使用,不代表这么做就是合理。实际将 JSON、XML 直接作用数据序列化通常并不是最优选择,因为它们在速度、效率、空间上并不是最优。换句话说它们更适合数据结构化而非数据序列化。

扯完 XML 和 JSON,我们来看看 ProtoBuf,同样的 ProtoBuf 也具有数据结构化的能力,其实也就是上面介绍的 message 定义。我们能够在 .proto 文件中,通过 message、import、内嵌 message 等语法来实现数据结构化,但是很容易能够看出,ProtoBuf 在数据结构化方面和 XML、JSON 相差较大,人类可读性较差,不适合上面提到的 XML、JSON 的一些应用场景。

但是如果从数据序列化的角度你会发现 ProtoBuf 有着明显的优势,效率、速度、空间几乎全面占优,看完后面的 ProtoBuf 编码的文章,你更会了解 ProtoBuf 是如何极尽所能的压榨每一寸空间和性能,而其中的编码原理正是 ProtoBuf 的关键所在,message 的表达能力并不是 ProtoBuf 最关键的重点。所以可以看出ProtoBuf重点侧重于数据序列化而非数据结构化。

最终对这些个人思考做一些小小的总结:

XML、JSON、ProtoBuf 都具有数据结构化和数据序列化的能力

XML、JSON 更注重数据结构化,关注人类可读性和语义表达能力。ProtoBuf 更注重数据序列化,关注效率、空间、速度,人类可读性差,语义表达能力不足(为保证极致的效率,会舍弃一部分元信息)

ProtoBuf 的应用场景更为明确,XML、JSON 的应用场景更为丰富。

我们先来看看官方文档给出的定义和描述:

protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。

Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

简单来讲, ProtoBuf 是结构数据序列化[1] 方法,可简单类比于 XML[2],其具有以下特点:

语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台

高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单

扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

protobuf3的变化

默认值

protobuf3 删除了 protobuf2 中用来设置默认值的 default 关键字,取而代之的是protobuf3为各类型定义的默认值,也就是约定的默认值,如下表所示:

类型

默认值

bool

false

整形

0

string

空字符串 ""

enum

第一个枚举元素的值,因为Protobuf3强制要求第一个枚举元素的值必须是0,所以枚举的默认值就是0

message

不是null,而是DEFAULT_INSTANCE

可以看出来,protobuf3定义的默认值跟Java中类的属性的默认值规则并不一样:Java中,如果类的属性类型是类,则该属性默认值是null,而protobuf3中,string、message的默认值都不是null。

枚举类型

不支持一个proto文件中,多个枚举中定义相同的枚举常量名

如下的两个枚举,定义在同一个proto文件中:

enum Enum1 {

IDLE = 0;

RUNNING = 1;

}

enum Enum2 {

IDLE = 5;

RUNNING = 6;

}

编译时,会报出错误:IDLE is already defined in "xxx",出现这一错误的原因就是:Protobuf3中不允许同一proto中,多个枚举中使用相同的枚举值。

枚举第一个常量的值必须是0

message类型

Java中,message类型的默认值是DEFAULT_INSTANCE,其值相当于空的message,即XXX.newBuilder().build(),这样对message类型的判空操作就应该是这样:

// protobuf message

message User {

int32 id = 1;

string name = 2;

string email = 3;

Address address = 4;

}

message Address {

string street = 1;

string building = 2;

}

// Java

if (user.getAddress() != null && user.getAddress() != UserProto.Address.getDefaultInstance()) {

...

} else {

...

}

Protobuf数据类型

基础类型

proto type

描述

java type

double

双精度

double

float

单精度

float

int32

32位整数,可变长度,编码负数效率低,编码负数推荐使用sint32

int

int64

64位整数,可变长度,编码负数效率低,编码负数推荐使用sint64

long

uint32

32位无符号整数,存储正数时与int32一致,用的较少,一般用int32,长度可变

int

uint64

64位无符号整数,存储正数与int64一致,用的较少,一般用int64,长度可变

long

sint32

有符号32位,长度可变,编码负数效率高

int

sint64

有符号64位,长度可变,编码负数效率高,存储正数时不推荐使用

long

fixed32

固定4个字节的长度,比uint32更有效率,存储正数时不推荐使用

int

fixed64

固定8个字节的长度,比uint64更有效率

long

sfixed32

有符号整数,固定4个字节

int

sfixed64

有符号整数,固定8个字节

long

bool

布尔值

boolean

string

字符串

String

bytes

字节数组

ByteString

枚举类型

枚举类型中必须包含至少一个元素,并且元素的编号必须从0开始。因为如果没有设置值的话,可以使用0作为默认值。

定义消息体

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "StudentProto3";

message Student {

string username = 1;

string password = 2;

string email = 3;

sint32 age = 4;

int64 timeSpane = 5;

double value = 6;

Address address = 7;

enum Gender {

MALE = 0;

FEMALE = 1;

}

Gender gender = 8;

}

message Address {

string province = 1;

string city = 2;

// 相当于java中的List

repeated string area = 3;

}

测试demo

StudentProto3.Student studentProto = StudentProto3.Student.newBuilder()

.setUsername("admin")

.setPassword("123456")

.setEmail("3306@qq.com")

.setValue(Double.MAX_VALUE)

// .setAge(Integer.MAX_VALUE)

.setAge(-2)

.setTimeSpane(System.currentTimeMillis())

.setAddress(address)

.setGender(StudentProto3.Student.Gender.MALE)

.build();

嵌套消息类型

Student.proto

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "StudentProto3";

message Student {

string username = 1;

string password = 2;

string email = 3;

sint32 age = 4;

int64 timeSpane = 5;

double value = 6;

Address address = 7;

}

message Address {

string province = 1;

string city = 2;

// 相当于java中的List

repeated string area = 3;

}

StudentPtoto3Demo.java

// 使用protobuf3序列化

StudentProto3.Address address = StudentProto3.Address.newBuilder()

.setProvince("北京")

.setCity("北京")

.addArea("chaoyang")

.addArea("miyun")

.build();

StudentProto3.Student studentProto = StudentProto3.Student.newBuilder()

.setUsername("admin")

.setPassword("123456")

.setEmail("3306@qq.com")

.setValue(Double.MAX_VALUE)

// .setAge(Integer.MAX_VALUE)

.setAge(-2)

.setTimeSpane(System.currentTimeMillis())

.setAddress(address)

.build();

System.out.println(studentProto);

System.out.println(studentProto.toByteArray().length);

repeated类型

repeated相当于java中的List类型,在其内部定义的类型可以是任意的。

reserved类型

当定义文件中的一些字段需要移除,最好不要直接删除,而是使用reserved标记要删除的字段,如果有人使用了被标记删除的字段,编译器会报错。有两种标记删除方式:

根据字段顺序标记

message Demo {

reserved 2, 5, 9 to 11 // 字段顺序为2、5,以及9到11的标记为删除

}

根据字段名称标记

message Demo {

reserved "name", "age" // 字段名称为name和age的被标记为删除

}

Map类型

在ProtoBuf中可以定义Map类型,语法如下:

map map_field = N;

key_type可以是其他的Message类型,string类型,PB类型定义表中(scalar value type)除了浮点类型和byte字节类型以外的其他类型。

需要注意以下几点:

枚举类型不能够作为key_type,value_type可以是除了Map以外的其他任何类型

map不能定义为repeated类型

map不保证顺序

定义proto文件

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "MapProto3";

message MapPerson {

map projects = 1;

}

message Project {

string name = 1;

int32 age = 2;

}

测试demo

public class MapProtoDemo {

public static void main(String[] args) {

MapProto3.Project p1 = MapProto3.Project.newBuilder()

.setName("neo")

.setAge(22)

.build();

MapProto3.Project p2 = MapProto3.Project.newBuilder()

.setName("mary")

.setAge(33)

.build();

MapProto3.MapPerson student = MapProto3.MapPerson.newBuilder()

.putProjects("student", p1)

.putProjects("teacher", p2)

.build();

System.out.println(student);

}

}

oneof类型

oneof关键字内部可以定义多个field,在使用的时候只能设置一个值。

定义消息体

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "OneOfProto3";

message Test1 {

string name = 1;

int32 age = 2;

oneof test_oneof {

Request req = 3;

Response rep = 4;

}

}

message Request {

string req = 1;

}

message Response {

string rep = 1;

}

测试demo

public class OneOfProtoDemo {

public static void main(String[] args) throws InvalidProtocolBufferException, Descriptors.DescriptorValidationException {

OneOfProto3.Request request = OneOfProto3.Request.newBuilder()

.setReq("request")

.build();

OneOfProto3.Response response = OneOfProto3.Response.newBuilder()

.setRsp("response")

.build();

OneOfProto3.Test1 neo = OneOfProto3.Test1.newBuilder()

.setName("neo")

.setAge(22)

// rep和rsp只能设置其中的一个

// .setReq(request)

.setRsp(response)

.build();

System.out.println(neo);

OneOfProto3.Test1 test1 = OneOfProto3.Test1.parseFrom(neo.toByteArray());

}

}

package包定义

Package包定义,可以防止Message重名问题,类似于java中的包。在java中使用Protobuf的包,有以下两种方式:

使用package关键字定义protobuf的模板消息包名,这种方式可以在多个语言中使用

syntax = "proto3";

package bar.foo;

option java_outer_classname = "OneOfProto3";

message PackageProto {

string name = 1;

int32 age = 2;

}

我们在使用的时候需要如下做:

package com.ray.protobufdemo.entity;

// 导入package处声明的包

import bar.foo.OneOfProto3;

public class PackageProtoDemo {

public static void main(String[] args) {

OneOfProto3.PackageProto.Builder builder = OneOfProto3.PackageProto.newBuilder();

}

}

使用option java_package语句声明java的包名,该用法是java独有的,如果不定义则使用package中的包路径

option java_package= "com.ray.protobufdemo";

import语法

在Protobuf中,不同的消息可以分别写在不同的proto文件中,在使用的时候可以使用关键字import引用其他消息模板。

protobuf的使用

定义消息的格式

// 声明使用proto3协议,如果不指定则默认使用proto2协议

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "AddressBook";

message Person {

string name = 1;

int32 id = 2;

string email = 3;

enum PhoneType {

// 在proto3中,第一个枚举值的序号必须为0

MOBILE = 0;

HOME = 1;

WORK = 2;

}

message PhoneNumber {

string number = 1;

PhoneType type = 2;

}

repeated PhoneNumber phone = 4;

}

message AddressBook {

// 在AddressBook message中引用另一个message Person

repeated Person person = 1;

}

protobuf消息格式说明:Person消息定义指定了三个字段(名称/值对),每一个字段对应于要包含在这种类型的消息中的数据。每个字段都有一个名称和一个类型,以及一个序号。

指定字段类型

在上例中,所有字段都是标量类型:两个整数(page_number和result_per_page)和一个字符串(query)。但是,您也可以为字段指定复合类型,包括枚举和其他消息类型。

分配字段编号

如您所见,消息定义中的每个字段都有一个唯一的编号。这些字段编号用于以二进制格式标识您的字段,一旦您的消息类型被使用,就不应该被更改。请注意,1到15范围内的字段编号需要一个字节来编码,包括字段编号和字段类型(您可以在协议缓冲区编码中找到更多信息)。16到2047范围内的字段编号需要两个字节。因此,您应该为经常出现的消息元素保留数字1到15。记住为将来可能添加的频繁出现的元素留出一些空间。

那protobuf是怎么做到向前及向后兼容的呢?靠的就是这个字段的编号,在反序列化的时候,protobuf会从输入流中读取出字段编号,然后再设置message中对应的值。如果读出来的字段编号是message中没有的,就直接忽略,如果message中有字段编号是输入流中没有的,则该字段不会被设置。所以即使通信的两端存在一方比另一方多出编号,也不会影响反序列化。但是如果两端同一编号的字段规则或者字段类型不一样,那就肯定会影响反序列化了。所以一般调整proto文件的时候,尽量选择加字段或者删字段,而不是修改字段编号或者字段类型。

您可以指定的最小字段编号为1,最大字段编号为229 - 1,即536,870,911。但是不能使用数字19000到19999 ( FieldDescriptor::kFirstReservedNumber 到FieldDescriptor::kLastReservedNumber),因为它们是为协议缓冲区实现而保留的-如果您在 .proto文件中使用这些保留的数字之一,协议缓冲区编译器就会报错。同样,您也不能使用任何保留字段。

指定字段规则

消息字段可以是以下字段之一:

singular: 可以有零个或其中一个字段(但不超过一个)。

repeated: 该字段可以重复任意次数(包括零次)。重复值的顺序将保留在Protocol Buffer中,将重复字段视为动态大小的数组。protobuf处理这个字段的时候,另外加了一个count计数变量,用于标明这个字段有多少个,这样发送方发送的时候,同时发送了count计数变量和这个字段的起始地址,接收方在接受到数据之后,按照count来解析对应的数据即可。

在java中使用protobuf3

安装idea插件

14cc690b9235dba660f290d2130a6e7b.png

添加pom依赖

com.google.protobuf

protobuf-java

3.4.0

kr.motd.maven

os-maven-plugin

1.6.2

org.xolstice.maven.plugins

protobuf-maven-plugin

0.5.0

${project.basedir}/src/main/protobuf

com.google.protobuf:protoc:3.1.0:exe:${os.detected.classifier}

compile

定义message

// user.proto

// 定义protobuf

syntax = "proto3";

option java_package = "com.ray.bigdata.protobuf";

// 指定生成的java类名

option java_outer_classname = "DemoModel";

message User {

int32 id = 1;

string name = 2;

string sex = 3;

}

测试demo

package com.ray.bigdata.canal;

import com.google.protobuf.InvalidProtocolBufferException;

import com.ray.bigdata.protobuf.DemoModel;

/**

* 使用protobuf进行数据的序列化和反序列化

*/

public class ProtobufDemo {

public static void main(String[] args) throws InvalidProtocolBufferException {

// 实例化protobuf对象

DemoModel.User.Builder builder = DemoModel.User.newBuilder();

// 给user对象进行赋值

builder.setId(1);

builder.setName("张三");

builder.setSex("男");

// 获取user对象的属性值

DemoModel.User userBuilder = builder.build();

System.out.println(userBuilder.getId());

System.out.println(userBuilder.getName());

System.out.println(userBuilder.getSex());

/**

* 数据的序列化和反序列化

* 序列化:可以将对象转换成字节码数据存储到kafka中

* 反序列化:可以将kafka中的数据消费出来,转换为java对象使用

*/

// 将一个对象序列化成二进制的字节码数据存储到kafka中

byte[] bytes = builder.build().toByteArray();

for (byte b: bytes) {

System.out.println(b);

}

// 将kafka中消费的数据反序列化

DemoModel.User user = DemoModel.User.parseFrom(bytes);

System.out.println(user);

System.out.println(user.getName());

System.out.println(user.getSex());

}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/378659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

根据DbSchema生成代码2

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Threading; using System.IO; using Rocky;namespace Rocky.CodeBuilder {public class DbBuilder : Disposable{#region 字段public even…

22-随机抽样一致算法RANSAC

随机抽样一致算法(Random sample consensus,RANSAC) 看似复杂,其基本思想就是:随机选取俩点,然后连接,给定一个容忍范围,在这个范围内的点越多越好,然后不断的迭代进行找两点之间容忍范围内点最…

treeset比较器_Java TreeSet比较器()方法与示例

treeset比较器TreeSet类的compare()方法 (TreeSet Class comparator() method) comparator() method is available in java.util package. 比较器()方法在java.util包中可用。 comparator() method is used to get the Comparator object based on customizing order the eleme…

智能车复工日记【1】——菜单索引回顾

博主联系方式: QQ:1540984562 QQ交流群:892023501 群里会有往届的smarters和电赛选手,群里也会不时分享一些有用的资料,有问题可以在群里多问问。 菜单回顾 1、系列文章解析结构元素菜单图示菜单缺点:1、系列文章 【智能车Code review】—曲率计算、最小二乘法拟合 【智能…

[转载]Oracle 11g R1下的自动内存经管(2)

AMM调整 除现有的用于内存经管的V$视图外,Oracle 11g还新添加了下面4个视图用于自动内存经管: ? ◆V$MEMORY_CURRENT_RESIZE_OPS ? ◆V$MEMORY_DYNAMIC_COMPONENTS ? ◆V$MEMORY_RESIZE_OPS ? ◆V$MEMORY_TARGET_ADVICE 转载于:https://www.cnblogs.…

23-背景建模

帧差法 由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标&#xf…

DB2 9 运用开辟(733 考试)认证指南,第 3 部门: XML 数据独霸(4)

议决运用顺序存储和检索 XMLXML 编码字符编码在汗青上,术语 字符集、字符编码 和 码页 都有雷同的意义:一个字符集和一个二进制码集,其中每个码示意一个字符。(码页是来自 IBM 的一个术语,示意一个大型主机或 IBM PC 上…

system.setin_Java System类setIn()方法及示例

system.setin系统类setIn()方法 (System class setIn() method) setIn() method is available in java.lang package. setIn()方法在java.lang包中可用。 setIn() method is used to assign again the standard input stream. setIn()方法用于再次分配标准输入流。 setIn() met…

Opencv——霍夫变换以及遇到的一些问题

目录问题1 :颜色空间转换函数参数问题:CV_BGR2GRAY vs CV_GRAY2BGR问题2:cvRound()、cvFloor()、cvCeil()函数用法霍夫变换的含义标准霍夫直线变换霍夫线变换函数参数讲解累计概率霍夫变换霍夫变换圆变换原理和算法步骤:霍夫圆变换…

java ssm如何上传图片_ssm整合-图片上传功能(转)

本文介绍 ssm (SpringSpringMVCMybatis)实现上传功能。以一个添加用户的案例介绍(主要是将上传文件)。一、需求介绍我们要实现添加用户的时候上传图片(其实任何文件都可以)。文件名:以 博客名日期的年月日时分秒毫秒形式命名如 言曌博客2017082516403213.png路径&am…

宏定义和内联函数区别

内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。 宏定义不检查函数参数,返回值什么的,只是…

24-光流估计

光流是空间运动物体在观测成像平面上的像素运动的“瞬间速度”,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪 亮度恒定:同一点随着时间的变化,其亮度不会发生改变 小运动:随着时间的…

java公平索非公平锁_java中的非公平锁不怕有的线程一直得不到执行吗

首先来看公平锁和非公平锁,我们默认使用的锁是非公平锁,只有当我们显示设置为公平锁的情况下,才会使用公平锁,下面我们简单看一下公平锁的源码,如果等待队列中没有节点在等待,则占有锁,如果已经…

mybatis.net - 5 嵌入资源与引用资源

在SqlMap.config文件中可以有两种方式引入外部的文件。 一种是通过资源的方式&#xff0c;在文件中表现为 resource&#xff0c;就是引用外部的文件&#xff0c;这里需要保证文件的路径正确。 <sqlMaps><sqlMap resource"Maps/ProductMap.xml"/><sqlM…

智能车复工日记【3】:图像处理——基本扫线和基本特征提取和十字补线

博主联系方式: QQ:1540984562 QQ交流群:892023501 群里会有往届的smarters和电赛选手,群里也会不时分享一些有用的资料,有问题可以在群里多问问。 目录 1、系列文章2、前言3、基本扫线(除了进入环岛状态或者坡道或者十字路口的普通扫线)1.基本数据和初步特征4、进一步特征…

short 用equals_Java Short类equals()方法的示例

short 用equals短类equals()方法 (Short class equals() method) equals() method is available in java.lang package. equals()方法在java.lang包中可用。 equals() method is used to check equality or inequality of this Object against the given Object or in other wo…

图解MySQL数据库的陈列和把持-4

泉源&#xff1a;网海拾贝 填入一些测试数据&#xff1a; 封闭“MySQL Query Browser”&#xff0c;再从头翻开它&#xff0c;切换到testtable表&#xff0c;看到了没有&#xff1f;刚刚输出的中文变成了“&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&am…

非常好友(C++)

Bessie和其他的所有奶牛的耳朵上都戴有一个射频识别&#xff08;RFID&#xff09;序列号码牌。因此农夫John可以机械化地计算他们的数量。很多奶牛都有一个“牛友”。如果奶牛A的序列号的约数之和刚好等于奶牛B的序列号&#xff0c;那么A的牛友就是B。在这里&#xff0c;一个数…

智能车复工日记【2】——普通PID、变结构PID、微分先行PID、模糊PID、专家PID

博主联系方式: QQ:1540984562 QQ交流群:892023501 群里会有往届的smarters和电赛选手,群里也会不时分享一些有用的资料,有问题可以在群里多问问。 目录 系列文章前言普通PID舵机参数:电机参数:变结构PI控制(电机控制,这里对公式进行修改采用正态分布公式)微分先行PID(…

爬动的蠕虫(C++)

问题描述&#xff1a; 一条虫子在n英寸深的井底&#xff0c;每次一分钟爬行u英寸&#xff0c;但是它再次爬行前必须先休息1分钟&#xff0c;在休息过程中它将滑落d英寸&#xff0c;在反复向上爬行和休息后&#xff0c;多长时间虫子能爬出这口井&#xff1f;在此过程中&#xf…