Java序列化,看这篇就够了

1.什么是序列化

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程:java

  • 序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心做用是对象状态的保存与重建。
  • 反序列化:客户端从文件中或网络上得到序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,经过反序列化重建对象。

2.序列化优势

一:对象序列化能够实现分布式对象。算法

主要应用例如:RMI(即远程调用Remote Method Invocation)要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时同样。数据库

二:java对象序列化不只保留一个对象的数据,并且递归保存对象引用的每一个对象的数据。json

能够将整个对象层次写入字节流中,能够保存在文件中或在网络链接上传递。利用对象序列化能够进行对象的"深复制",即复制对象自己及引用的对象自己。序列化一个对象可能获得整个对象序列。后端

三:序列化能够将内存中的类写入文件或数据库中。api

好比:将某个类序列化后存为文件,下次读取时只需将文件中的数据反序列化就能够将原先的类还原到内存中。也能够将类序列化为流数据进行传输。安全

总的来讲就是将一个已经实例化的类转成文件存储,下次须要实例化的时候只要反序列化便可将类实例化到内存中并保留序列化时类中的全部变量和状态。网络

四:对象、文件、数据,有许多不一样的格式,很难统一传输和保存。框架

序列化之后就都是字节流了,不管原来是什么东西,都能变成同样的东西,就能够进行通用的格式传输或保存,传输结束之后,要再次使用,就进行反序列化还原,这样对象仍是对象,文件仍是文件。前后端分离

什么场景下会用到序列化

  • 暂存大对象

  • Java对象须要持久化的时候

  • 须要在网络,例如socket中传输Java对象。由于数据只可以以二进制的形式在网络中进行传输,所以当把对象经过网络发送出去以前须要先序列化成二进制数据,在接收 端读到二进制数据以后反序列化成Java对象

  • 深度克隆(复制)

  • 跨虚拟机通讯

3.如何使用序列化

经过上面的介绍你们已经了解了什么是序列化,以及为何要使用序列化。这一节咱们一块儿来学习一下如何使用序列化。

首先咱们要把准备要序列化类,实现 Serializabel接口,至于为何要实现Serializabel接口,咱们后面再详细介绍。

咱们如今想要将Person序列化,Person类以下:

package com.wugongzi.day0112;

import java.io.Serializable;

/**
 * add by wugongzi 2021/1/22
 */
public class Person implements Serializable {
    private int id;
    private String name;
    private int age;


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

序列化:

package com.wugongzi.day0112;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 序列化Person
 * add by wugongzi 2021/1/22
 */
public class SerializationTest {
    public static void main(String[] args) throws IOException {
        Person p1 = new Person(1, "jack", 19);
        Person p2 = new Person(2, "mary", 22);
        List<Person> list = new ArrayList();
        list.add(p1);
        list.add(p2);

        // 建立文件流
        FileOutputStream fos = new FileOutputStream("/Users/File/person.txt");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(list);
        os.close();
        System.out.println("serialization  success");
    }
}

这里的person.txt就是序列化到本地的文件(打开会是乱码)

反序列化:

package com.wugongzi.day0112;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 反序列化Person
 * add by wugongzi 2021/1/22
 */
public class DeserializationTest {

    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("/Users/File/person.txt");
        ObjectInputStream is = new ObjectInputStream(fis);
        Object obj = null;
        List<Person> list = new ArrayList<>();
        try {
            list = (List<Person>)is.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        is.close();

        //遍历list,输出
        for (Person person:list){
            System.out.println(person.toString());
        }

    }

}

输出结果:

Person{id=1, name='jack', age=19}
Person{id=2, name='mary', age=22}

序列化流程:

 1)定义一个类,实现Serializable接口;

 2)在程序代码中建立对象后,建立对象输出流ObjectOutputStream对象并在构造参数中指定流的输出目标(好比一个文件),经过objectOutputStream.writeObject(obj)把对象序列化并输出到流目标处;

 3)在须要提取对象处:建立对象输入流ObjectInputStream对象并在构造参数中指定流的来源,而后经过readObject()方法获取对象,并经过强制类型转换赋值给类对象引用。

4.序列化原理

序列化算法会按步骤执行如下事情:

  1)当前类描述的元数据输出为字节序列;【类定义描述、类中属性定义描述】

  2)超类描述输出为字节序列;【若是超类还有超类,则依次递归,直至没有超类】

  3)从最顶层超类往下,依次输出各种属性值描述,直至当前类对象属性值。

  即:从下到上描述类定义,从上往下输出属性值。

5.为何Java序列化要实现Serializable

首先咱们来看一下Serializable接口源码:

package java.io;
public interface Serializable {
}

对,你没有看错,就是一个空接口。

既然这个接口里面什么东西都没有,那么实现这个接口意义何在呢?读到这里或许有不少同窗会产生疑问:

一个空接口,里面啥都没有。为何java设计的时候必定要实现Serializable才能序列化?不能去掉Serializable这个接口,让每一个对象都能序列化吗?

比较有说服力的解释是:

总的就是说安全性问题,假如没有一个接口(即没有Serializable来标记是否能够序列化),让全部对象均可以序列化。那么全部对象经过序列化存储到硬盘上后,均可以在序列化获得的文件中看到属性对应的值(后面将会经过代码展现)。因此最后为了安全性(即不让一些对象中私有属性的值被外露),不能让全部对象均可以序列化。要让用户本身来选择是否能够序列化,所以须要一个接口来标记该类是否可序列化。

6.几个须要注意的点

1)静态变量和transient关键字修饰的变量不能被序列化;

​ 序列化时并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,所以 序列化并不保存静态变量。transient做用是控制变量的序列化,在变量声明前加上该关键字,能够阻止该变量被序列化到文件中,在被反序列化后,transient变量的值设为初始值,如int型的是0。

2)反序列化时要按照序列化的顺序重构对象:如先序列化A后序列化B,则反序列化时也要先获取A后获取B,不然报错。

3)serialVersionUID(序列化ID)的做用:决定着是否可以成功反序列化。

  虚拟机是否容许对象反序列化,不是取决于该对象所属类路径和功能代码是否与虚拟机加载的类一致,而是主要取决于对象所属类与虚拟机加载的该类的序列化 ID 是否一致

  java的序列化机制是经过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,若是相同则认为是一致的,即可以进行反序列化,不然就会报序列化版本不一致的异常。

4)自定义序列化方法的应用场景:对某些敏感数据进行加密操做后再序列化;反序列化对加密数据进行解密操做。

5)重复序列化:同一个对象重复序列化时,不会把对象内容再次序列化,而是新增一个引用指向第一次序列化时的对象而已。

6)序列化实现深克隆:在java中存在一个Cloneable接口,经过实现这个接口的类都会具有clone的能力,同时clone在内存中进行,在性能方面会比咱们直接经过new生成对象要高一些,特别是一些大的对象的生成,性能提高相对比较明显。

7.常见的序列化技术

一、java 序列化

  优势:java语言本省提供,使用比较方面和简单

  缺点:不支持跨语言处理、性能相对不是很好,序列化之后产生的数据相对较大

二、XML序列化

  XML序列化的好处在于可读性好,方便阅读和调试。可是序列化之后的 字节码文件比较大,并且效率不高,适应于对性能不高,并且QPS较低的企业级内部系统之间的数据交换的场景,同时XML又具备语言无关性,因此还能够用于异构系统之间的数据交换和协议。好比咱们熟知的WebService,就是采用XML格式对数据进行序列化的

三、JSON序列化

  JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于XML来讲,JON的字节流较小,并且可读性也很是好。如今JSON数据格式的其余运用最广泛的。序列化方式还衍生了阿里的fastjson,美团的MSON,谷歌的GSON等更加优秀的转码工具。

四、Hessian 序列化框架子

  Hessian是一个支持跨语言传输的二进制序列化协议,相对于Java默认的序列化机制来讲,Hessian具备更好的性能和易用性,并且支持多重不一样的语言,实际上Dubbo采用的就是Hessian序列化来实现,只不过Dubbo对Hessian进行重构,性能更高。

五、Protobuf 序列化框架

  Protobuf是Google的一种数据交换格式,它独立于语言、独立于平台。

  Google 提供了多种语言来实现,好比 Java、C、Go、Python,每一种实现都包含了相应语言的编译器和库文件Protobuf 使用比较普遍,主要是空间开销小和性能比较好,很是适合用于公司内部对性能要求高的 RPC 调用。 另外因为解析性能比较高,序列化之后数据量相对较少,因此也能够应用在对象的持久化场景中可是可是要使用 Protobuf 会相对来讲麻烦些,由于他有本身的语法,有本身的编译器。

选型建议

  ① 对性能要求不高的场景,能够采用基于 XML 的 SOAP 协议

  ② 对性能和间接性有比较高要求的场景,那么Hessian、Protobuf、Thrift、Avro 均可以

  ③ 基于先后端分离,或者独立的对外的 api 服务,选用 JSON 是比较好的,对于调试、可读性都很不错

  ④ Avro 设计理念偏于动态类型语言,那么这类的场景使用 Avro 是能够的