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,一经查实,立即删除!

相关文章

22-随机抽样一致算法RANSAC

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

23-背景建模

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

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

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

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

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

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

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

24-光流估计

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

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

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

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

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

图解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;一个数…

使用快捷键,快到极致

前段时间曾经写过一篇文章&#xff0c;《优秀程序员无他-善假于物也》。其中谈到一点是优秀的程序员必须要能灵活的掌握常用软件的快捷键。对于程序员来说&#xff0c;每天使用时间最长的软件恐怕就是IDE&#xff08;Integrated Development Environment&#xff09;了。如果你…

轮廓(查找和绘制轮廓、轮廓的表达与组织、轮廓的特性)

目录1、轮廓的定义2、如何在图像中找到轮廓opencv自带的查找轮廓函数:findContours()3、轮廓的表达方式1.顶点的序列2.Freeman链码4、轮廓之间的组织方式5、轮廓的特点&#xff08;这部分可以展开来详细探讨&#xff0c;这里不做过多解释&#xff09;6、轮廓的匹配7、绘制轮廓d…

01-基本配置与测试

一、开发环境 使用的是Anaconda 3&#xff0c;其中Python版本为3.6.3 首先&#xff0c;在Anaconda下的Scripts文件夹下打开命令框 使用pip install -U selenium安装selenium 打开Jupiter Notebook&#xff0c;输入from selenium import webdriver进行测试 不报错即安装成功 …

Opencv——查找并绘制凸包、凸包与轮廓的关系

定义 给定二维平面上的点集&#xff0c;凸包就是将最外层的点连接起来构成的凸多边型。 理解物体形状或轮廓的一 种比较有用的方法便是计算一个物体的凸包&#xff0c;然后计算其凸缺陷(convexity defects)。 检测凸包 opencv自带函数&#xff1a;convexHull() 参数解释&a…

java接口并发衡量_java 后端设计高并发接口总结

如何设置高并发接口一、并发队列的选择二、请求接口的合理设计三、高并发下的数据安全3.1 超发的原因3.2 悲观锁思路3.3 FIFO队列思路3.4 乐观锁思路一、并发队列的选择Java的并发包提供了三个常用的并发队列实现&#xff0c;分别是&#xff1a;ArrayBlockingQueue、Concurrent…

JAVA 取得当前目录的路径

在写java程序时不可避免要获取文件的路径...总结一下,遗漏的随时补上 1.可以在servlet的init方法里 String path getServletContext().getRealPath("/"); 这将获取web项目的全路径 例如 :E:/eclipseM9/workspace/tree/ tree是我web项目的根目录 2.你也可以随时在任意…

02.1-元素定位(find)

常用的一些方法 一、导包 from selenium import webdriver二、打开火狐&#xff08;空白页&#xff09; b webdriver.Firefox()三、跳转到指定的网站 b.get(https://www.baidu.com/)四、将浏览器页面最大化 b.maximize_window()五、通过F12可查看当前的贴吧为超链接形式 …

快速傅里叶变换(FFT)——按时间抽取DIT的基

目录【1】前言1、DIF计算量2、利用性质改善【2】公式推导1、N 到 2*N/2a、分解原序列b、分解后的DFT变换c、一系列化简操作之后d、蝶形信号流e、计算量总结2、N/2 到 2*N/4a、分解X2(k)序列b、蝶形信号流&#xff08;2列&#xff09;3、N/4 到 2*N/8a、蝶形信号流&#xff08;3…

快速傅里叶变换(FFT)——按频率抽取DIF的基

目录【1】回顾DIT【2】算法原理【3】运算特点【1】回顾DIT https://blog.csdn.net/qq_42604176/article/details/105559756 【2】算法原理 设序列点数&#xff1a;N2^M,M为正整数。将输入序列按照前一半、后一半分开。&#xff08;并非按照奇偶分&#xff09; 由于&#xf…

莫比乌斯函数---C++

【问题描述】 莫比乌斯函数&#xff0c;数论函数&#xff0c;由德国数学家和天文学家莫比乌斯(Mobius&#xff0c;1790-1868)提出。梅滕斯(Mertens)首先使用μ(n)作为莫比乌斯函数的记号。而据说&#xff0c;高斯(Gauss)比莫比乌斯早三十年就曾考虑过这个函数。莫比乌斯函数在数…