博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java序列化机制之protoStuff框架
阅读量:4035 次
发布时间:2019-05-24

本文共 7546 字,大约阅读时间需要 25 分钟。

之前曾经写了两篇java的序列的机制,一种是默认的java序列化机制,这种方式效率太低。另外一种是谷歌的protobuf,但是这种我们还要写proto文件,并且我们还要使用工具来编译生成java文件,实在太麻烦。但是protostuff却不一样,能够很好的解决上面两者的问题。这篇文章就研究一下如何去使用,并对其进行一个简单的分析。

一、认识protostuff

其实protostuff也是有局限性的,比如说在序列化的文件在10M以下的时候,还是使用java自带的序列化机制比较好,但是文件比较大的时候还是protostuff好一点,这里的10M不是严格的界限。

protostuff也是谷歌的产品,它是基于protobuf发展而来的,相对于protobuf提供了更多的功能和更简易的用法。

废话不多说,直接看一下protoStuff是如何使用的吧。

二、代码实现

环境准备:添加依赖或者是jar

io.protostuff
protostuff-core
1.6.0
io.protostuff
protostuff-runtime
1.6.0

这里我使用了maven,直接添加依赖即可,如果你没有使用maven,在百度上搜索相应的jar包就好了。

1、定义要序列化的bean

首先是学生类

public class Student {
private String name; private int age; public Student(String name, int age) {
this.name = name; this.age = age; } //getter和setter方法 //toString方法}

然后是学校类

public class School {
private String schoolName; private List
students; public School(String schoolName, List
students) {
this.schoolName = schoolName; this.students = students; } //getter和setter方法 //toString方法}

在这里我们真正要序列化的是School,但是为了使得例子更有说服力,于是就在School里面定义了Student。

2、protoStuff序列化工具类

public class ProtostuffUtils {
//避免每次序列化都重新申请Buffer空间 private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); //缓存Schema private static Map
, Schema
> schemaCache = new ConcurrentHashMap
, Schema
>(); //序列化方法,把指定对象序列化成字节数组 @SuppressWarnings("unchecked") public static
byte[] serialize(T obj) {
Class
clazz = (Class
) obj.getClass(); Schema
schema = getSchema(clazz); byte[] data; try { data = ProtostuffIOUtil.toByteArray(obj, schema, buffer); } finally { buffer.clear(); } return data; } //反序列化方法,将字节数组反序列化成指定Class类型 public static
T deserialize(byte[] data, Class
clazz) { Schema
schema = getSchema(clazz); T obj = schema.newMessage(); ProtostuffIOUtil.mergeFrom(data, obj, schema); return obj; } @SuppressWarnings("unchecked") private static
Schema
getSchema(Class
clazz) { Schema
schema = (Schema
) schemaCache.get(clazz); if (schema == null) { schema = RuntimeSchema.getSchema(clazz); if (schema == null) { schemaCache.put(clazz, schema); } } return schema; }}

这其实就是一个工具类,这里面的代码可以不用更改,直接拿过来就可以使用了。不过在这里有必要对里面的一些字段方法等进行一个说明。

(1)字段LinkedBuffer

这个字段表示,申请一个内存空间用户缓存,LinkedBuffer.DEFAULT_BUFFER_SIZE表示申请了默认大小的空间512个字节,我们也可以使用MIN_BUFFER_SIZE,表示256个字节。

(2)字段schemaCache

这个字段表示缓存的Schema。那这个Schema是什么呢?就是一个组织结构,就好比是数据库中的表、视图等等这样的组织机构,在这里表示的就是序列化对象的结构。

(3)方法serialize

public static 
byte[] serialize(T obj) {
Class
clazz = (Class
) obj.getClass(); Schema
schema = getSchema(clazz); byte[] data; try {
data = ProtostuffIOUtil.toByteArray(obj, schema, buffer); } finally {
buffer.clear(); } return data;}

它是序列化方法,里面的代码很容易理解,首先获得要序列化对象的类,然后为其分配一个缓存空间,其次获得这个类的Schema。最后一行代码ProtostuffIOUtil.toByteArray进行序列化。

(4)方法deserialize

//反序列化方法,将字节数组反序列化成指定Class类型    public static 
T deserialize(byte[] data, Class
clazz) {
Schema
schema = getSchema(clazz); T obj = schema.newMessage(); ProtostuffIOUtil.mergeFrom(data, obj, schema); return obj;}

表示反序列化,反序列里面的代码更简单了,首先根据序列化对象获取其组织结构Schema。然后根据byte直接mergeFrom成一个对象。

(5)方法getSchema

private static 
Schema
getSchema(Class
clazz) {
Schema
schema = (Schema
) schemaCache.get(clazz); if (schema == null) {
schema = RuntimeSchema.getSchema(clazz); if (schema == null) {
schemaCache.put(clazz, schema); } } return schema;}

获取序列化对象的组织结构。

3、测试

public class Test {
public static void main(String[] args) {
Student stu1 = new Student("张三",20); Student stu2 = new Student("李四",21); List
students = new ArrayList
(); students.add(stu1); students.add(stu2); School school = new School("西工大",students); //首先是序列化 byte[] bytes = ProtostuffUtils.serialize(school); System.out.println("序列化后: " + bytes.length); //然后是反序列化 School group1 = ProtostuffUtils.deserialize(bytes,School.class); System.out.println("反序列化后: " + school.toString()); }}

运行一下就能出现结果,很简单。上面的ProtostuffUtils是一个工具类,你可以保留下来,复制粘贴到任何地方使用。下面的小结是对其底层原理的解析,如果你只是简单的使用,到此就OK了。如果想深入了解可以接着往下看

三、protoStuff底层是如何实现序列化的?

上面只是给出了一个基本的使用,并且对Protostuff序列化工具类中的字段和方法进行了一个简单的介绍,在这里我们深入的去分析一下到底底层是如何实现序列化和反序列化的,

在上面序列化方法中,最核心的其实就是最后一句:data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);如何实现序列化其实就在于这个toByteArray方法,我们深入这个方法中看看:

public static 
byte[] toByteArray(T message, Schema
schema, LinkedBuffer buffer){
if (buffer.start != buffer.offset) throw new IllegalArgumentException("Buffer previously used and had not been reset."); final ProtostuffOutput output = new ProtostuffOutput(buffer); try{
//这才是实现序列化的核心 schema.writeTo(output, message); } catch (IOException e){
throw new RuntimeException("Serializing to a byte array threw an IOException " + "(should never happen).", e); } return output.toByteArray();}

我们可以看到schema.writeTo(output, message);是真正的核心,我们继续追进去看看:

public final void writeTo(Output output, T message) throws IOException{
for (Field
f : getFields()) f.writeTo(output, message);}

原来里面还有一层,没关系真正实现序列化的源头马上就要浮出水面了

@Overridepublic void writeTo(Output output, T message) throws IOException{
CharSequence value = (CharSequence)us.getObject(message, offset); if (value != null) output.writeString(number, value, false);}

看到了吧其实就是把序列化对象信息保存成CharSequence,然后序列化。

对于反序列化呢?核心ProtostuffIOUtil.mergeFrom(data, obj, schema);我们也追进去看看

public static 
void mergeFrom(byte[] data, T message, Schema
schema){
IOUtil.mergeFrom(data, 0, data.length, message, schema, true); }

想要弄清楚,就想继续追进去看:

static 
void mergeFrom(byte[] data, int offset, int length, T message, Schema
schema, boolean decodeNestedMessageAsGroup){
try{
final ByteArrayInput input = new ByteArrayInput(data, offset, length, decodeNestedMessageAsGroup); // 继续跟进 schema.mergeFrom(input, message); input.checkLastTagWas(0); } catch (ArrayIndexOutOfBoundsException ae){
throw new RuntimeException("Truncated.", ProtobufException.truncatedMessage(ae)); } catch (IOException e){
throw new RuntimeException("Reading from a byte array threw"+ "an IOException (should never happen).", e); }}

继续进去看看

@Overridepublic final void mergeFrom(Input input, T message) throws IOException{
// 按顺序获取字段 for (int n = input.readFieldNumber(this); n != 0; n = input.readFieldNumber(this)){
final Field
field = getFieldByNumber(n); if (field == null){
input.handleUnknownField(n, this); } else{
field.mergeFrom(input, message); } }}

OK,真正马上出来了,有点耐心继续跟进去:

public void mergeFrom(Input input, T message)throws IOException{
// 负载给字段 us.putObject(message, offset, input.readString());}

到了这一步了,应该就明白了吧。

在这里插入图片描述

转载地址:http://mmbdi.baihongyu.com/

你可能感兴趣的文章
arm linux 生成火焰图
查看>>
linux和windows内存布局验证
查看>>
linux config
查看>>
linux insmod error -1 required key invalid
查看>>
linux kconfig配置
查看>>
linux不同模块completion通信
查看>>
linux printf获得时间戳
查看>>
C语言位扩展
查看>>
linux dump_backtrace
查看>>
linux irqdebug
查看>>
git 常用命令
查看>>
linux位操作API
查看>>
uboot.lds文件分析
查看>>
uboot start.s文件分析
查看>>
没有路由器的情况下,开发板,虚拟机Ubuntu,win10主机,三者也可以ping通
查看>>
本地服务方式搭建etcd集群
查看>>
安装k8s Master高可用集群
查看>>
忽略图片透明区域的事件(Flex)
查看>>
忽略图片透明区域的事件(Flex)
查看>>
AS3 Flex基础知识100条
查看>>