简述几种序列化方式

概述

在Java应用中,所有对象的创建都是在内存中完成的,当应用需要保存对象到磁盘文件或通过网络发送给其他应用时,需要将对象信息转化成二进制字节流,这个从对象状态转化成二进制字节流的过程,就是序列化。相反,从字节流创建成对象的过程就是反序列化。

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

在Java语言中,二进制字节流是byte[],所有类的实例化都是对象,序列化就是对象转成二进制串过程,反序列化就是重新将二进制串转成对象。

一般情况下,我们需要从几个方面来评价序列化方式的特性,如:

  • 通用性 是否支持跨语言、跨平台;
  • 性能 空间和时间的开销,序列化后的数据大小常常影响着后续的传输和存储性能;解析的时间也影响着序列化的性能;
  • 兼容性 系统升级会使某一实体的属性变更,会不会导致序列化异常;

常见序列化方式

public interface Serialization {      // 序列化      byte[] serialize(Object obj) throws IOException;      // 反序列化      <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException, ClassNotFoundException;}

Java原生序列化,直接上代码!

public class Person implements Serializable {    private String name;    private String uid;    private String pwd;    @Override    public String toString() {        return "Person{" +                   "name='" + name + '\'' +                   ", uid='" + uid + '\'' +                   ", pwd='" + pwd + '\'' +                   '}';    }}
public class JdkSerialization implements Serialization {    @Override    public byte[] serialize(Object obj) throws IOException {        ByteArrayOutputStream bos = new ByteArrayOutputStream();        ObjectOutputStream out = new ObjectOutputStream(bos);        out.writeObject(obj);        out.flush();        return bos.toByteArray();    }    @Override    public <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException, ClassNotFoundException {        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));        return (T) in.readObject();    }}
public static void main(String[] args) throws IOException, ClassNotFoundException {    Person person = new Person();    person.setUid("4321");    person.setName("飞天");    person.setPwd("1234");    Serialization serialization = new JdkSerialization();    byte[] bytes = serialization.serialize(person);    Person readPerson = serialization.deserialize(bytes,Person.class);    System.out.println(readPerson);}

输出:Person{name='飞天', uid='4321', pwd='1234'}

Java原生序列化方式,主要由ObjectInputStream和ObjectOutputStream实现,他们可以直接装饰文件I/O(RandomAccessFile)或者网络I/O(Socket),来实现将对象存储到文件或者在网络中传输。不过,还有几点需要注意:

  • 序列化对象需实现Serialization接口
  • static属性不能被序列化,序列化保存对象的状态,static属于类状态
  • transient修饰的不能被序列化
  • 版本号serialVersionUID
    • 版本号一致,新增字段不影响反序列化对象
    • 版本号不一致,影响反序列化对象,将报错

Kryo是一个快速有效的Java二进制对象图序列化框架。主要有序列化反序列化更高效、序列化之后字节数据更小、更易用等特点。应用场景对象存入文件、数据库,或者在网络中传输。

Maven依赖:

<dependency>    <groupId>com.esotericsoftware</groupId>    <artifactId>kryo</artifactId>    <version>4.0.1</version></dependency><dependency>    <groupId>com.esotericsoftware</groupId>    <artifactId>kryo-shaded</artifactId>    <version>4.0.1</version></dependency>

序列化实现:

public class KryoSerialization implements Serialization {    @Override    public byte[] serialize(Object obj) throws IOException {        Kryo kryo = kryoLocal.get();        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();        Output output = new Output(byteArrayOutputStream);        kryo.writeObject(output, obj);        output.close();        return byteArrayOutputStream.toByteArray();    }    @Override    public <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException {        Kryo kryo = kryoLocal.get();        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);        Input input = new Input(byteArrayInputStream);        input.close();        return kryo.readObject(input, clazz);    }    private static final ThreadLocal<Kryo> kryoLocal = ThreadLocal.withInitial(() -> {        Kryo kryo = new Kryo();        kryo.setReferences(true);        kryo.setRegistrationRequired(false);        return kryo;    });}

继续上面Person对象,使用kyro序列化方式:

Serialization serialization = new KryoSerialization();byte[] bytes = serialization.serialize(person);Person readPerson = serialization.deserialize(bytes, Person.class);System.out.println(readPerson);

Kyro的Output和Input类,也是一个装饰器类,可以内置Java IO的InputStream和OutputStream,也可以实现网络传输和存入文件。Kyro广泛用在Rpc框架中,如Dubbo框架。

  • Rpc框架比较关注的是性能,扩展性,通用性,Kyro的性能与其他几种序列化方式对比中表现较好;
  • Kyro的Api也比较友好;
  • 不过,Kyro兼容性不是很好,使用时应注意序列化和反序列化两边的类结构是否一致
  • Kyro序列化时,不需要对象实现Serializable

Hessian是一个基于HTTP的高性能RPC框架,其序列化算法叫Hessian协议,是业界公认的一种高效率高压缩比的序列化方式,如:Dubbo框架就支持Hessian序列化方式。

public class Hessian2Serialization implements Serialization {
    @Override    public byte[] serialize(Object obj) throws IOException {        ByteArrayOutputStream bos = new ByteArrayOutputStream();        Hessian2Output out = new Hessian2Output(bos);        out.writeObject(obj);        out.flush();        return bos.toByteArray();    }
    @Override    public <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException {        Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(bytes));        return (T) input.readObject(clazz);    }}
  • Hessian序列化后的数据要比Kyro序列化后的数据大;
  • 但要比Java原生序列化方式好很多;
  • Hessian跨语言支持比较好
  • Hessian需要实体类实现Serializable接口
  • 注:Hessian序列化包含 BigDecimal 字段的对象时会导致其值一直为0

Protocol Buffers是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。现阶段支持C++、JAVA、Python等三种编程语言。对象序列化成Protocol Buffer之后可读性差,但是相比xml,json,它占用小,速度快,适合做数据存储或 RPC 数据交换格式。

protostuff是一个基于protobuf实现的序列化方法,它较于protobuf最明显的好处是,在几乎不损耗性能的情况下做到了不用我们写.proto文件来实现序列化。

public class ProtostuffSerialization implements Serialization {
    private Objenesis objenesis = new ObjenesisStd();
    @Override    public byte[] serialize(Object obj) throws IOException {        Class clazz = obj.getClass();        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);        try {            Schema schema = RuntimeSchema.createFrom(clazz);            return ProtostuffIOUtil.toByteArray(obj, schema, buffer);        } catch (Exception e) {            throw e;        } finally {            buffer.clear();        }    }
    @Override    public <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException {        T message = objenesis.newInstance(clazz);        Schema<T> schema = RuntimeSchema.createFrom(clazz);        ProtostuffIOUtil.mergeFrom(bytes, message, schema);        return message;    }}
  • 安全性较好,相比于其他几种序列化方式,只有Protobuf还没有出现过漏洞,如:Java原生的反序列化方式,如果对未知来源的数据进行反序列化,将产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行
  • 性能比kyro稍差,兼容性好于kyro,性能优于JSON、Hessian、Java原生序列化方式

JSON( JavaScript Object Notation )是一种轻量级的数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。使用最多的场景是用于Web服务和客户端浏览器之间进行数据交换,如:前端使用Ajax以Json格式向服务端发起请求,服务端以Json格式响应给客户端,客户端根据Json数据格式解析响应内容。当然,在网络中传输仍然需要转化成字节,不过很多语言都提供类包支持将JSON串转化成字节流,(注:JSON串相当于一个满足JSON数据格式的字符串),如Java的FastJson、JavaScript的eval()函数等。还有一些Nosql数据库、消息队列也支持Json序列化方式,如Redis存储对象时,使用JSON格式,使数据支持跨平台、可读性也更强。

public class FastJsonSerialization implements Serialization {
    static final String charsetName = "UTF-8";
    @Override    public byte[] serialize(Object obj) throws IOException {        SerializeWriter out = new SerializeWriter();        JSONSerializer serializer = new JSONSerializer(out);        serializer.config(SerializerFeature.WriteEnumUsingToString, true);        serializer.config(SerializerFeature.WriteClassName, true);//<1>        serializer.write(obj);        return out.toBytes(charsetName);    }
    @Override    public <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException {        return JSON.parseObject(new String(bytes), clazz);    }}

XML是一种很常见的数据保存方式,我经常用它来保存一些数据,或者是一些配置参数。由于XML具有优秀的跨平台、可读性好的特点,可用于构建基本的Web Services平台,不同于RPC框架,Web Services是基于HTTP协议的,通过SOAP协议,使运行在不同的操作系统并使用不同的技术和编程语言的应用程序可以互相进行通信。SOAP是基于XML为序列化和反序列化协议的结构化消息传递协议。Web Services还使用网络服务描述语言—WSDL(Web Services Description Language),用于描述Web Services以及如何访问Web Services,WSDL基于XML语言格式。Web services使用XML来编解码数据,并使用SOAP来传输数据。

序列化新面孔

Avro是Hadoop的一个子项目。Avro设计用于支持数据密集型应用程序的数据格式,并具有很好的跨语言性,Avro数据通过与语言无关的schema来定义,schema通过JSON来描述,解析数据时使用schema,数据被序列化成二进制文件或JSON文件。序列化效率与Google的protobuffer相当。当数据密集型应用使用RPC进行网络传输时,Avro支持远程过程调用(RPC)协议。

Spearal是一个新的开源的序列化协议,这个协议旨在初步替换JSON 将HTML和移动应用连接到Java的后端。Spearal的主要目的是提供一个序列协议,这个协议即使是在端点间传输的复杂的数据结构中也可以简单工作:我们很快就能看到JSON的一些局限将会害了开发者, 这些局限是不会发生在一个好的通用的序列化格式中的。抛开这个主要目的,Spearal还提供了在标准JSON中没有的高级功能,如局部对象序列化、内建的对JPA的非初始化关联、不同型号的协调、对象特性过滤等。虽然还在初期发展阶段,但是Spearal再将HTML应用连接到Java后端上已经很有用了。


  1. https://www.infoq.cn/article/serialization-and-deserialization
  2. https://www.infoq.cn/article/2014/10/spearal-serialization-protocol
  3. https://github.com/EsotericSoftware/kryo
  4. https://blog.csdn.net/fanjunjaden/article/details/72823866
  5. https://github.com/cytle/blog/blob/master/source/_posts/Hessian-2-0%E5%BA%8F%E5%88%97%E5%8C%96%E5%8D%8F%E8%AE%AE%E8%A7%84%E8%8C%83.md
  6. https://zhuanlan.zhihu.com/p/55116858
  7. https://www.infoworld.com/article/3275924/oracle-plans-to-dump-risky-java-serialization.html
  8. https://protostuff.github.io/docs/
  9. http://www.w3school.com.cn/soap/soap_intro.asp
  10. https://www.cnblogs.com/breg/p/4748140.html
  11. https://www.oschina.net/translate/beyond-json-introducing?print

原文发布于微信公众号 - BanzClub(banz-club)

原文发表时间:2019-05-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券