java 序列化 protobuf_在Java中使用protobuf序列化对象

什么是protobuf

它是一个对象序列化/反序列化的工具,什么是对象的序列化/反序列化?就是把一个Java堆中存活的对象转换成一串二进制编码,然后该编码可以用于本地存储和网络传输。反序列化就是根据一串二进制编码还原出原来的那个对象,protobuf能够将一个对象以特定的格式转换为一个二进制串(序列化),然后将二进制串还原成对象(反序列化)。这里涉及到两个指标:

对同一个目标对象:

1)序列化和反序列化的时间开销,

2)序列化之后串的长度

protobuf在这两个方面都有非常出色的表现(网传)

在Windows下使用protobuf的步骤如下:

第一步:

下载protoc-2.5.0-win32.zip,得到其中的protoc.exe.然后将该protoc.exe的存放路径加入Path环境变量,便于访问。比如,我的protoc.exe存放于D:/protobuf,环境变量中添加如下配置:

D:/protobuf

第二步:

编写.proto文件,它是序列化一个对象的“模板”,protobuf就是根据它来决定如何序列化和反序列化。

编写的person-entity.proto配置文件如下:

option java_outer_classname = "PersonEntity";//生成的数据访问类的类名

message Person {

required int32 id= 1;//同上

required string name = 2;//必须字段,在后面的使用中必须为该段设置值

optional string email = 3;//可选字段,在后面的使用中可以自由决定是否为该字段设置值

}

message字段代表了一个对象,所以,可以使用message实现对象的嵌套序列化

required表示是强制字段,在后面的使用中必须为该字段设置值;

optional表示是可选字段,在后面的使用中可选地为该字段设置值;

repeated表示集合类型,可以填充多个数据

后面的1,2,3是字段的编号,字段名是让用户使用的,字段编号则是让系统识别的,从1开始。

protobuf自定义的数据类型和Java的数据类型的对应关系见这篇博客https://blog.csdn.net/chuhui1765/article/details/100670318

可以指定字段类型为其他的Message字段类型:

message SearchResponse{

repeated Result result= 1;

}

message Result{

requiredstring url = 1;

optionalstring title = 2;

repeatedstring snippets = 3;

}

可以在message内部嵌套另一个message:

message SearchResponse{

message Result{

requiredstring url = 1;

optionalstring title = 2;

repeatedstring snippets = 3;

}

repeated Result result= 1;

}

有关protobuf更详细的用法,可以参考这篇优秀的博文https://worktile.com/tech/share/prototol-buffers

第三步:

用protoc.exe生成PersonEntity.class。打开DOS窗口,输入如下的编译命令:

protoc.exe -I=D:/protobuf --java_out=D:/protobuf D:/protobuf/person-entity.proto

编译命令的格式如下:

protoc.exe -I=.protoc文件的存放路径 --java_out=.class文件的输出路径 .protoc文件的具体存放路径

其中第三个路径是必须的,而且要写明.protoc文件

最后会生成PersonEntity.java文件,可以把它理解为一个工具类,帮助我们执行对象的序列化。

第四步:

新建maven项目,将PersonEntity.java复制到项目中,引入maven依赖

com.google.protobuf

protobuf-java

2.5.0

利用工具类序列化和反序列化对象。

/*author:chxy

data:2020/4/1

description:*/import java.io.IOException;public classTest {public static voidmain(String[] args) throws IOException {//模拟将对象转成byte[],方便传输

PersonEntity.Person.Builder builder =PersonEntity.Person.newBuilder();

builder.setId(1);

builder.setName("abc");

builder.setEmail("def");

PersonEntity.Person person=builder.build();

System.out.println("after :" +person.toString());

finalbyte[] bytes =person.toByteArray();

System.out.println("===========Person Byte==========");for(byteb : person.toByteArray()){

System.out.print(b);

}

System.out.println();//反序列化成Person类

byte[] byteArray =person.toByteArray();

PersonEntity.Person person2=PersonEntity.Person.parseFrom(byteArray);

System.out.println("after :" +person2.toString());

}

}

这里还犯过一个错:另外自定义一个Person类与PersonEntity.Person相对应。实际上在我们的项目中可以直接利用这个PersonEntity.Person来实现功能需求。

下面同样以这个Person类序列化为例,来实际地比较通过各种不同的序列化方式得到的的字节数组长度:

1.protobuf

序列化长度为:

System.out.println(person.getSerializedSize());

结果为:12

2.Java原生序列化

/*author:chxy

data:2020/4/2

description:*/import java.io.*;public classTest2 {public static voidmain(String[] args) throws IOException {

Person p= new Person(1,"abc","def");

ObjectOutputStream objectOutputStream= newObjectOutputStream(new FileOutputStream("E:/object.txt"));

objectOutputStream.writeObject(p);

FileInputStream fileInputStream= new FileInputStream("E:/object.txt");

finalint length =fileInputStream.read();

System.out.println(length);

}

}

结果为:172

3.通过对象的toString()

序列化:重写对象的toString()方法,按照特定的格式,将对象的各个字段封装进一个String字符串,再将字符串按照特定的编码方式,转换成byte数组,实现本地存储和网络传输。

反序列化:对byte数组,重新转换成字符串,再将字符串按照约定的格式,还原成对象的属性,从而构造出原来的对象。

这里重点探讨的是:String对象转换成byte数组之后的长度

public static voidmain(String[] args) {

String s= "abc";

System.out.println(s.getBytes().length);

String s2= "嘤嘤嘤";

System.out.println(s2.getBytes().length);

System.out.println(Charset.defaultCharset());

}

输出结果为:

3

9

UTF-8

可见,如果是英文字母每个字符占一个字节,如果是汉字,每个字符占3个字节。如果通过这种方式序列化,它的空间效率也是非常高的。

这里有必要搞清楚Java默认的字符编码集:

/**

* Encodes this {@code String} into a sequence of bytes using the

* platform's default charset, storing the result into a new byte array.

*

*

The behavior of this method when this string cannot be encoded in

* the default charset is unspecified. The {@link

* java.nio.charset.CharsetEncoder} class should be used when more control

* over the encoding process is required.

*

* @return The resultant byte array

*

* @since JDK1.1*/

public byte[] getBytes() {return StringCoding.encode(value, 0, value.length);

}

Java采用平台的默认字符编码集来进行字符与二进制byte序列的转换。当然也可以在序列化的时候显示指定字符编码集。

Unicode 与 UTF-8

为什么会出现unicode,这首先要从ASCII码说起,它起源最早,用7位来表示英文字母、数字等字符。但是后来又出现了很多其他语言的字符,比如汉字,ASCII码最多只能表示256个字符,无法支持这些”异域“字符,因此出现了unicode码,将各种语言的字符用一种统一的编码方式转换成01序列。但是新的问题又出现了,unicode编码效率不高,原来一个字符a用ASCII表示只需要一个字符,现在用unicode可能用3个字符,存储效率和网络传输效率大打折扣。因此出现了中间转换编码集,utf-8就是其中之一。”UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,又称万国码“,注意,它的编码长度是可变的。所以,unicode是utf-8的基础,utf-8是对unicode的一种解释。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值