一文搞懂序列化与反序列化

序列化与反序列化

一、是什么

序列化:就是将对象转化成字节序列的过程。

反序列化:就是讲字节序列转化成对象的过程。

对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象。

画个图理解一下!
在这里插入图片描述

二、为什么

那么为什么要去进行序列化呢?有以下两个原因

  1. 持久化:对象是存储在JVM中的堆区的,但是如果JVM停止运行了,对象也不存在了。序列化可以将对象转化成字节序列,可以写进硬盘文件中实现持久化。在新开启的JVM中可以读取字节序列进行反序列化成对象。
  2. 网络传输:网络直接传输数据,但是无法直接传输对象,可在传输前序列化,传输完成后反序列化成对象。所以所有可在网络上传输的对象都必须是可序列化的。

举个例子:
将int num=100这个int类型数据保存在文件中,注意不是保存100这个数字,而是要保存int 类型的100,并且能够从文件中直接恢复int 类型的100。(这里你可能会说数字100不就是int类型的吗?你在文件中直接写入100它也可能会是String类型的,其类型是不确定的!)我们在保存一个数据的值时还希望能把他数据类型保存下来。
我们就需要用到序列化和反序列化

三、怎么实现

怎么去实现对象的序列化呢?
如果我们需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一!

  • Serializable
  • Externalizable

这里ObjectInputStream和ObjectOutputStream提供了对基本类型和对象类型的序列化和反序列化的方法
ObjectOutputStream提供序列化功能
ObjectOutputStream提供反序列化功能

下面分别实现Serializable和Externalizable接口来演示序列化和反序列化

1.实现Serializable接口

Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。而这一点对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。而Java作为一门面向对象的编程语言,对象作为其主要数据的类型载体,为了完成对象数据的读写操作,也就需要一种方式来让JVM知道在进行IO操作时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。

public class Dog implements Serializable {

    private String name;

    private int age;

	public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
 //省去toString
}

序列化:

public class ObjectOutputStream_ {

    public static void main(String[] args) {
//序列化后,保存文件的格式不是纯文本的,而是按照他的格式来保存的
        String filePath = "d:\\data.txt";

        ObjectOutputStream oos = null;
        try {
        //序列化对象到data.txt中
             oos = new ObjectOutputStream(new FileOutputStream(filePath));
        //保存一个dog对象
            oos.writeObject(new Dog("tom",3));
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (oos != null) {
                oos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行后可以在data.txt文件中看到序列化内容
在进行序列化后打开文件发现乱码,这是因为写入的文件是以二进制文件存储的,所以会乱码!

这里注意Dog类一定要实现Serializable 接口否则会报 NotSerializableException
这里推荐使用Serializable,因为他是个标记接口,它没有方法。Externalizable有方法需要实现。

反序列化:

//指定反序列化的文件
        String filePath = "d:\\data.txt";

        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(filePath));
          
            System.out.println(ois.readObject());

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            if (ois != null) {
                ois.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

输出结果
在这里插入图片描述

注意:

  1. 这里进行反序列化(读取)的顺序要和反序列化(保存数据)的顺序一致
  2. 序列化对象时,默认将里面所有的属性都进行序列化,但除了static或transient修饰的成员对于不想序列化的字段可以再字段类型之前加上transient关键字修饰(反序列化时会被赋予类型的默认值)
  3. 序列化对象时,要求其属性的类型也需要实现序列化接口(基本类型其包装类都默认实现了序列化接口,引用类型需实现序列化接口否则报错!)
  4. 序列化具备可继承性,也就是说如果父类实现了序列化接口,它的所有子类也默认实现了,无需实现序列化接口!

2.实现Externalizable接口

实现Externalizable接口必须重写两个方法

  • writeExternal(ObjectOutput out)
  • readExternal(ObjectInput in)
public class Dog implements Externalizable {

    private String name;

    private int age;
   //这里birth不进行序列化
    private Date birth;
    
    //可以自定义决定那些需要序列化
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age); 
    }
    //可以自定义决定那些需要反序列化 这里也要和序列化的顺序对应!!!
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = (String) in.readObject();
        this.age = in.readInt();
    }
    //省去toString方法有参无参构造
}

这里注意Serializable序列化时,static或transient修饰的属性不会被序列化,而Externalizable序列化只能通过writeExternal()和readExternal()方法指定序列化哪些属性不序列化哪些属性,不能使用transient修饰符;

序列化:

		String filePath = "d:\\data.txt";

        ObjectOutputStream oos = null;
        try {
            //序列化对象到data.txt中
            oos = new ObjectOutputStream(new FileOutputStream(filePath));
            //保存一个dog对象
            oos.writeObject(new Dog("旺财", 3,new Date()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (oos != null) {
                oos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

反序列化:

		String filePath = "d:\\data.txt";

        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(filePath));

            System.out.println(ois.readObject());

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            if (ois != null) {
                ois.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

运行结果:
在这里插入图片描述
这里birth未进行序列化给默认值null!

注意:

  1. 序列化对象要提供无参构造
  2. 如果序列化时一个字段没有序列化(writeExternal方法中未声明),那反序列化时要注意反序列化中也不能有该字段(readExternal方法中也不能有)

四、serialVersionUID(了解即可)

serialVersionUID :序列化的版本号,可以提高兼容性。举个例子:你在一个类中加了新的属性(前提已经在类上写了private static final long serialVersionUID = 1L;)有了这个序列号的话在处理时他会认为这个类是做了一个升级而不会认为是一个新的类。

serialVersionUID适用于java序列化机制。简单来说,JAVA序列化的机制是通过 判断类的serialVersionUID来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。

具体序列化的过程是这样的:序列化操作时会把系统当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败;

serialVersionUID的生成有三种方式:
一是默认的1L,比如:private static final long serialVersionUID = 1L;

二是根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long serialVersionUID = xxxxL;

三是隐式声明:未显式的声明serialVersionUID时java序列化机制会根据Class自动生成一个serialVersionUID(最好不要这样,因为如果Class发生变化,自动生成的serialVersionUID可能会随之发生变化,导致匹配不上)
注意:显示声明serialVersionUID可以避免对象不一致

序列化类增加属性时,最好不要修改serialVersionUID,避免反序列化失败

IDEA生成serialVersionUID

在类名上按alt+enter
在这里插入图片描述
如果不显示上图提示,可以按照下面步骤设置:
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值