序列化与反序列化

序列化的意义

  在考虑系统性能的时候,会考虑序列化。远程通信的时候,就要考虑序列化。序列化和反序列化是每天都会碰到的问题。就我而言,序列化这个概念基本上一直在听到,但是很少有了解。Java对象的传输、分布式架构、大数据量的工程。
  Java 平台允许我们在内存中创建可复用的Java 对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,所以这些对象的生命周期不会比JVM 的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能。
  序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化。
  反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化。
  那么如何去评价一个序列化工具呢?
  评价一个序列化算法优劣的两个重要指标是:序列化以后的数据大小;序列化操作本身的速度及系统资源开销(CPU、内存)。


常见的序列化工具

Java本身自带的序列化

  Java本身就有一个序列化工具(实现Serializable接口),但是缺点也是存在的:(1)序列化后数据比较大。(2)其他语言无法识别
  Java本身序列化方法:

  @Override
    public <T> byte[] serializer(T obj) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = null;
        try {
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return byteArrayIutputStream.toByteArray();
    }

    @Override
    public <T> T deSerializer(byte[] bytes, Class<T> clazz) {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInput objectInput = null;
        try {
            objectInput = new ObjectInputStream(byteArrayInputStream);
            return (T)objectInput.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

  对于一个希望通过序列化的类,可能会报InvalidClassException,这是因为序列化的类最好加上serialVersionUID,用来让系统判断序列化的可靠性。

serialVersionUID 的作用
  Java 的序列化机制是通过判断类的serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException如果没有为指定的class 配置serialVersionUID,那么java 编译器会自动给这个class 进行一个摘要算法,类似于指纹算法,只要这个文件有任何改动,得到的UID 就会截然不同的,可以保证在这么多类中,这个编号是唯一的。

  serialVersionUID 有两种显示的生成方式:
  一是默认的1L,比如:private static final long serialVersionUID = 1L;
  二是根据类名、接口名、成员方法及属性等来生成一个64 位的哈希字段。
  当实现java.io.Serializable 接口的类没有显式地定义一个serialVersionUID 变量时候,Java 序列化机制会根据编译的Class 自动生成一个serialVersionUID 作序列化版本比较用,这种情况下,如果Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化的。

  Java对象序列化过程中,如果序列化得到的对象增加或者减少一个变量,并不会报错,仅仅是某个字段读取不到。对于一个静态变量的序列化,静态变量不会参与序列化。因为序列化保存的是一个对象的状态,而静态变量属于一个类的状态。

transient

  transient修饰的字段表示不会在序列化过程被保存,他的值在反序列化之后仍是类定义的值。也可以手动写到流里面,来绕过序列化

序列化与继承

  如果说一个子类实现了序列化,父类没有实现序列化,在子类被反序列化之后,是无法获得父类的值,即子类继承父类已经被定义的那个值是空的。

  如果一个父类实现序列化,那么子类自动实现序列化,不用继承Serializable接口。

  对于同一个对象写入流两次,流里的数据不会加倍,而是增加五个字节(增加新增引用和一些控制信息的空间),因为当流里存在同一个对象的时候,只是会增加一个引用。这算是个优点,节省了存储空间。

序列化实现克隆

  Java对于每个接口类都具有克隆能力,但只是浅克隆。浅克隆只是新建对象,对原对象的一些变量只是复制它的引用。我实例2 克隆实例1,当实例2改变某个值后,实例1也会改变。深克隆实现Serializable接口,把对象序列化流中,再从流里读出来,这个对象就不是原来的对象了,所有的变量的引用都会新建一个引用。

xml序列化框架

  优点是可读性强,缺点是序列化之后的数据比较大。在技术要求比较高的时候,一般不会用到它。

  代码实例:

public class XmlSerializer  {

    XStream xStream = new XStream(new DomDriver());
    
    public <T> String serializer(T obj) {
        return xStream.toXML(obj);
    }


    public <T> T deSerializer(String bytes, Class<T> clazz) {
        return (T)xStream.fromXML(bytes);
    }

    public static void main(String[] args) {
        XmlSerializer iSerializer = new XmlSerializer();
        User user = new User();
        
        String bytes = iSerializer.serializer(user);
        user.setName("jolivan");
        System.out.println(new String(bytes));
        
        User userNow = iSerializer.deSerializer(bytes,User.class);
        System.out.println(userNow.getName());
        System.out.println(userNow.getAge());
    }
}
==============输出==================
<serial.User>
  <name>Lushe</name>
  <age>23</age>
</serial.User>
Lushe
23

  我们可以看到,根据数据我们就知道他是个什么类,里面有啥。可读性非常高。

JSON

  JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于XML 来说,JSON 的字节流更小,而且可读性也非常好。现在JSON数据格式在企业运用是最普遍的
  JSON 序列化常用的开源工具有很多
  1. Jackson (https://github.com/FasterXML/jackson)
  2. 阿里开源的FastJson (https://github.com/alibaba/fastjon)
  3. Google 的GSON (https://github.com/google/gson)
  这几种json序列化工具中,Jackson 与fastjson 要比GSON 的性能要好,但是Jackson、GSON 的稳定性要比Fastjson 好。而fastjson的优势在于提供的api 非常容易使用。

  用阿里的FastJson来示例一下:

public class FastjsonSerializeer implements ISerializer {


    @Override
    public <T> byte[] serializer(T obj) {
        return JSON.toJSONBytes(obj);
    }

    @Override
    public <T> T deSerializer(byte[] bytes, Class<T> clazz) {
        return (T)JSON.parseObject(bytes,clazz);
    }


    public static void main(String[] args) {
        ISerializer iSerializer = new FastjsonSerializeer();
        User user = new User();

        byte [] bytes = iSerializer.serializer(user);
        user.setName("jolivan");
        System.out.println(new String(bytes));

        User userNow = iSerializer.deSerializer(bytes,User.class);
        System.out.println(userNow.getName());
    }
}

hessian

  dubbo里使用的就是它,但对它做了优化,又称为hessian2。

Protobuf

  优势:(1)独立语言(可以基于不同的语言)、独立平台(跨平台交互)(2)性能高,压缩性好;(3)解析性好

  缺陷:实现起来很麻烦,学习成本大。

  它有独立的编译器

序列化的实际应用举例

  比如说我们在一个分布式系统中,有一个订单模块和一个支付模块,订单模块基于一个协议(dubbo)调用订单系统,底层传输的事二进制数据,那么我们要做的事情就是把对象转化为一个二进制数据,这就是序列化的场景。