序列化框架的选型和比对

简介: 序列化框架的选型和比对

序列化通信


将对象转换为字节数组,方便在网络中进行对象的传输。在网络通信中,不同的计算机进行相互通信主要的方式就是将数据流从一台机器传输给另外一台计算机,常见的传输协议包括了TCP,UDP,HTTP等,网络io的方式主要包括有了aio,bio,nio三种方式。

当客户端将需要请求的数据封装好了之后就需要进行转换为二进制格式再转换为流进行传输,当服务端接收到流之后再将数据解析为二进制格式的内容,再按照约定好的协议进行处理解析。最常见的场景就是rpc远程调用的时候,对发送数据和接收数据时候的处理。


下边我们来一一介绍一下现在比较常见的几款序列化技术框架。


jdk序列化



jdk自身便带有序列化的功能,Java序列化API允许我们将一个对象转换为流,并通过网络发送,或将其存入文件或数据库以便未来使用,反序列化则是将对象流转换为实际程序中使用的Java对象的过程。


先来看看实际的代码案例


首先我们创建一个基础的测试Person类


package com.sise.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
 * @author linhao
 * @date 2019/8/15
 * @Version V1.0
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person implements Serializable {
    private static final long serialVersionUID = 3829252771168681281L;
    private Integer id;
    private String username;
    private String tel;
}
复制代码


如果某些特殊字段不希望被序列化该如何处理?


这里面如果有相应的属性不希望被序列化操作的话,可以使用transient关键字进行修饰,例如希望tel属性不希望被序列化,可以改成这样:


private transient String tel;
复制代码


这样的话,该对象在反序列化出来结果之后,相应的属性就会为null值。


为什么要定义serialVersionUID?


序列化操作时,系统会把当前类声明的serialVersionUID写入到序列化文件中,用于反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的


serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。


如果没有定义serialVersionUID时


当实现当前类没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果类信息进行修改,会导致反序列化时serialVersionUID与原先值无法match,反序列化失败。


通过jdk提升的序列化对其进行相应的序列化和反序列化的代码案例


package com.sise.test.jdk;
import com.sise.test.Person;
import java.io.IOException;
/**
 * @author idea
 * @date 2019/8/15
 * @Version V1.0
 */
public class SerializationTest {
    /**
     *
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 2000; i++) {
            String fileName = "test-person.txt";
            Person person = new Person();
            person.setId(1);
            person.setTel("99562352");
            person.setUsername("idea");
            SerializationUtil.serialize(person, fileName);
            Person newPerson = (Person) SerializationUtil.deserialize(fileName);
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - begin));
    }
}
复制代码


jdk序列化的缺点


1.无法跨语言

这一缺点几乎是致命伤害,对于跨进程的服务调用,通常都需要考虑到不同语言的相互调用时候的兼容性,而这一点对于jdk序列化操作来说却无法做到。这是因为jdk序列化操作时是使用了java语言内部的私有协议,在对其他语言进行反序列化的时候会有严重的阻碍。


2.序列化之后的码流过大

jdk进行序列化编码之后产生的字节数组过大,占用的存储内存空间也较高,这就导致了相应的流在网络传输的时候带宽占用较高,性能相比较为低下的情况。

Hessian序列化框架



Hessian是一款支持多种语言进行序列化操作的框架技术,同时在进行序列化之后产生的码流也较小,处理数据的性能方面远超于java内置的jdk序列化方式。


相关的代码案例:


package com.sise.test.hessian;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sise.test.Person;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
 * @author idea
 * @date 2019/8/15
 * @Version V1.0
 */
public class HessianTest {
    /**
     *
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 2000; i++) {
            Person person = new Person();
            person.setId(1);
            person.setUsername("idea");
            person.setTel("99562352");
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            HessianOutput ho = new HessianOutput(os);
            ho.writeObject(person);
            byte[] userByte = os.toByteArray();
            ByteArrayInputStream is = new ByteArrayInputStream(userByte);
            //Hessian的反序列化读取对象
            HessianInput hi = new HessianInput(is);
            Person newPerson = (Person) hi.readObject();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - begin));
    }
}
复制代码


Hessian的源码里面,核心主要还是com.caucho.hessian.io里面的代码,

AbstractSerializer是Hessian里面的核心序列化类,当我们仔细查看源码的时候就会发现hessian提供了许多种序列化和反序列化的类进行不同类型数据的处理。(我使用的是hessian4.0,因此相应的类会多很多)


网络异常,图片无法展示
|


在SerializerFactory里面有getSerializer和getDefaultSerializer的函数,专门用于提取这些序列化和反序列化的工具类,这样可以避免在使用该工具类的时候又要重新实例化,这些工具类都会被存储到不同的ConcurrentHashMap里面去。


网络异常,图片无法展示
|


ps:对于hessian3.0时候的Serializer/Derializer实现功能没有考虑到对于异常信息进行序列化处理,因此如果遇到相应问题的朋友可以考虑将hessian的版本提升到3.1.5以上。


Kryo序列化技术



Kryo是一种非常成熟的序列化实现,已经在Twitter、Groupon、 Yahoo以及多个著名开源项目(如Hive、Storm)中广泛的使用,它的性能在各个方面都比hessian2要优秀些,因此dubbo后期也开始渐渐引入了使用Kryo进行序列化的方式。


对于kryo的使用,我们来看看相应代码:


首先我们引入相应的依赖:


<dependency>
      <groupId>com.esotericsoftware</groupId>
      <artifactId>kryo-shaded</artifactId>
      <version>3.0.3</version>
    </dependency>
复制代码


然后就是基础的序列化和反序列化代码操作了


package com.sise.test.kryo;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.sise.test.Person;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
/**
 * @author idea
 * @date 2019/8/15
 * @Version V1.0
 */
public class KryoTest {
    public static void main(String[] args) throws FileNotFoundException {
        Kryo kryo=new Kryo();
        Output output = new Output(new FileOutputStream("person.txt"));
        Person person=new Person();
        person.setId(1);
        person.setUsername("idea");
        kryo.writeObject(output, person);
        output.close();
        Input input = new Input(new FileInputStream("person.txt"));
        Person person1 = kryo.readObject(input, Person.class);
        input.close();
        System.out.println(person1.toString());
        assert "idea".equals(person1.getUsername());
    }
}
复制代码


ps:

这里我们需要注意,Kryo不支持没有无参构造函数的对象进行反序列化,因此如果某个对象希望使用Kryo来进行序列化操作的话,需要有相应的无参构造函数才可以。


由于Kryo不是线程安全,因此当我们希望使用Kryo构建的工具类时候,需要在实例化的时候注意线程安全的问题。代码案例:


package com.sise.test.kryo;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.sise.test.Person;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/**
 * @author idea
 * @data 2019/8/17
 */
public class KryoUtils {
    public byte[] serialize(Object obj){
        Kryo kryo = kryos.get();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Output output = new Output(byteArrayOutputStream);
        kryo.writeClassAndObject(output, obj);
        output.close();
        return byteArrayOutputStream.toByteArray();
    }
    public <T> T deserialize(byte[] bytes) {
        Kryo kryo = kryos.get();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Input input = new Input(byteArrayInputStream);
        input.close();
        return (T) kryo.readClassAndObject(input);
    }
    private static final ThreadLocal<Kryo> kryos=new ThreadLocal<Kryo>(){
        @Override
        protected Kryo initialValue(){
            Kryo kryo=new Kryo();
            return kryo;
        }
    };
    public static void main(String[] args) {
        KryoUtils kryoUtils=new KryoUtils();
        for(int i=0;i<1000;i++){
            Person person=new Person(1,"idea");
            byte[] bytes=kryoUtils.serialize(person);
            Person newPerson=kryoUtils.deserialize(bytes);
            System.out.println(newPerson.toString());
        }
    }
}
复制代码


XStream实现对象的序列化



在使用XStream进行序列化技术的实现过程中,类中的字符串组成了 XML 中的元素内容,而且该对象还不需要实现 Serializable 接口。XStream不关心被序列化/反序列化的类字段的可见性,该对象也不需要有getter/setter方法和默认的构造函数。


引入的依赖:


<dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.4.9</version>
    </dependency>
复制代码


通过使用XStream来对对象进行序列化和反序列化操作:


package com.sise.test.xstream;
import com.sise.test.Person;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
/**
 * @author idea
 * @date 2019/8/15
 * @Version V1.0
 */
public class XStreamTest {
    private static XStream xStream;
    static {
        xStream = new XStream(new DomDriver());
        /*
         * 使用xStream.alias(String name, Class Type)为任何一个自定义类创建到类到元素的别名
         * 如果不使用别名,则生成的标签名为类全名
         */
        xStream.alias("person", Person.class);
    }
    //xml转java对象
    public static Object xmlToBean(String xml) {
        return xStream.fromXML(xml);
    }
    //java对象转xml
    public static String beanToXml(Object obj) {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xStream.toXML(obj);
    }
    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 2000; i++) {
            Person person = new Person();
            person.setId(1);
            person.setUsername("idea");
            String xml = XStreamTest.beanToXml(person);
            Person newPerson = (Person) XStreamTest.xmlToBean(xml);
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - begin));
    }
}
复制代码


google的Protobuf



google protobuf是一个灵活的、高效的用于序列化数据的协议。相比较XML和JSON格式,protobuf更小、更快、更便捷。google protobuf是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可以编译成Java、python、C++、C#、Go等代码,然后就可以直接使用,不需要再写其他代码,自带有解析的代码。


protobuf相对于kryo来说具有更加高效的性能和灵活性,能够在实际使用中,当对象序列化之后新增了字段,在反序列化出来的时候依旧可以正常使用。(这一点kryo无法支持)


不同序列化框架的总结



Jdk Hessian Kryo Xstream Protobuf
优点 使用方便,序列化包含的信息较多较全,安全性较高 产生的码流较小,支持跨语言 速度较快,而且序列化后的码流较小 对于被序列化对象的要求较低,运行跨语言之间使用 产生的码流小,支持跨语言,速度快,灵活性高
缺点 产生的码流过大,网络传输占用带宽,消耗性能,不支持跨语言的序列化处理。 性能比jdk序列化方式好,但是效率依然不高 对于循环引用的情况需要将reference开启,开启之后性能会有所降低 序列化的耗时较久,性能不高 需要进行环境安装和搭建


目前已有的序列化框架还有很多在文中没有提到,日后假若在开发中遇到的时候可以适当的进行归纳总结,比对各种不同的序列化框架之间的特点。

目录
相关文章
|
自然语言处理 Java Go
Fury:一个基于JIT动态编译的高性能多语言原生序列化框架
Fury是一个基于JIT动态编译的多语言原生序列化框架,支持Java/Python/Golang/C++等语言,提供全自动的对象多语言/跨语言序列化能力,以及相比于别的框架最高20~200倍的性能。
Fury:一个基于JIT动态编译的高性能多语言原生序列化框架
|
3月前
|
Java
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
|
3月前
|
开发框架 缓存 前端开发
基于SqlSugar的开发框架循序渐进介绍(24)-- 使用Serialize.Linq对Lambda表达式进行序列化和反序列化
基于SqlSugar的开发框架循序渐进介绍(24)-- 使用Serialize.Linq对Lambda表达式进行序列化和反序列化
|
3月前
|
XML 存储 JSON
(十二)探索高性能通信与RPC框架基石:Json、ProtoBuf、Hessian序列化详解
如今这个分布式风靡的时代,网络通信技术,是每位技术人员必须掌握的技能,因为无论是哪种分布式技术,都离不开心跳、选举、节点感知、数据同步……等机制,而究其根本,这些技术的本质都是网络间的数据交互。正因如此,想要构建一个高性能的分布式组件/系统,不得不思考一个问题:怎么才能让数据传输的速度更快?
|
缓存 自然语言处理 Rust
比JDK最高快170倍,蚂蚁集团开源高性能多语言序列化框架Fury
Fury是一个基于JIT动态编译和零拷贝的多语言序列化框架,支持Java/Python/Golang/JavaScript/C++等语言,提供全自动的对象多语言/跨语言序列化能力,和相比JDK最高170倍的性能。经过多年蚂蚁核心场景的锤炼打磨,现已正式在Github对外开源:https://github.com/alipay/fury
2576 5
|
消息中间件 NoSQL 安全
【Spring技术原理】采用protostuff和kryo高性能序列化框架实现RestTemplate的序列化组件
【Spring技术原理】采用protostuff和kryo高性能序列化框架实现RestTemplate的序列化组件
287 0
【Spring技术原理】采用protostuff和kryo高性能序列化框架实现RestTemplate的序列化组件
|
JSON 缓存 Java
Android 序列化框架 Gson 原理分析,可以优化吗?
Gson 是 Google 推出的 Java Json 解析库,具有接入成本低、使用便捷、功能扩展性良好等优点,想必大家都很熟悉了。在这篇文章里,我们将讨论 Gson 的基本用法和以及主要流程的源码分析。
344 0
|
JSON Dart 安全
【Flutter框架】项目的手动序列化小项目以及对于进程异步性和格式化代码的研究
【Flutter框架】项目的手动序列化小项目以及对于进程异步性和格式化代码的研究
|
缓存 Java 测试技术
第03篇:手写JavaRPC框架之搞定序列化
天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄!从本篇开始后面的所有章节都是实战环节,每节一个小目标,最终我们实现完整的JavaRPC的框架,然后发布maven仓库,感兴趣的同学可以下载研究。
210 0
|
存储 分布式计算 Hadoop
实现 MapReduce 框架的序列化
实现 MapReduce 框架的序列化