什么是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的一种解释。