【Java笔记】 深入理解序列化和反序列化

深入理解序列化和反序列化

1.是什么

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

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

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。

2.为什么

那么为什么要去进行序列化操作呢?有两个原因:

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

3.怎么做

怎么去实现对象的序列化呢?

Java为我们提供了对象序列化的机制,规定了要实现序列化对象的类要满足的条件和实现方法。

  1. 对于要序列化对象的类必须实现Serializable接口或者Externalizable接口
  2. 实现方法:JDK提供的ObjectOutputStreamObjectInputStream来实现序列化和反序列化

3.1 实现Serializable接口

public class Student implements Serializable {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

序列化

ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

构造方法

  • public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。

构造举例,代码如下:

FileOutputStream fileOut = new FileOutputStream("stu.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

2.写出对象方法

  • public final void writeObject (Object obj) : 将指定的对象写出。
public class SerializeDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("stu.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        Student student = new Student();
        student.setAge(18);
        student.setName("xiaoxin");
        oos.writeObject(student);
        
        fos.close();
        oos.close();
    }
}

反序列化

ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

  • public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
  • 如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject () : 读取一个对象。

public class DeserializationDemo {
    public static void main(String[] args) throws Exception {

        FileInputStream fis = new FileInputStream("stu.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Student student = (Student) ois.readObject();

        fis.close();
        ois.close();

        System.out.println(student);// Student{name='xiaoxin', age=18}
    }
}

3.2 实现Externalizable接口

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

  • writeExternal(ObjectOutput out)
  • readExternal(ObjectInput in)

如果序列化时一个字段没有序列化,那反序列化是要注意别给为序列化的字段反序列化了

举个栗子

public class Student implements Externalizable {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(age);
        out.writeObject(name);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.age=in.readInt();
        this.name= (String) in.readObject();
    }
}

序列化

public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("stu.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Student student = new Student();
        student.setAge(18);
        student.setName("xiaoxin");
        oos.writeObject(student);
        oos.close();
        fos.close();
    }

反序列化

public static void main(String[] args) throws Exception {
        FileInputStream fis = new FileInputStream("stu.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Student student = (Student) ois.readObject();
        System.out.println(student);
        ois.close();
        fis.close();
    }

3.3 注意知识点

  1. 一个对象要进行序列化,如果该对象成员类型是引用类型的,那这个引用类型也一定要是可序列化的,否则会报错

  2. 同一个对象多次序列化成字节序列,这多个字节序列反序列化成的对象还是一个(使用==判断为true)(因为所有序列化保存的对象都会生成一个序列化编号,当再次序列化时会去检查此对象是否已经序列化了,如果是,那序列化只会输出上个序列化的编号)

  3. 如果序列化一个可变对象,序列化之后,修改对象属性值,再次序列化,只会保存上次序列化的编号(这是个坑注意下)

  4. 对于不想序列化的字段可以再字段类型之前加上transient关键字修饰(反序列化时会被赋予默认值)

  5. 另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

    • 该类的序列版本号与从流中读取的类描述符的版本号不匹配

    • 该类包含未知数据类型

    • 该类没有可访问的无参数构造方法

3.4 serialVersionUID的作用

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

先讲述下序列化的过程:在进行序列化时,会把当前类的serialVersionUID写入到字节序列中(也会写入序列化的文件中),在反序列化时会将字节流中的serialVersionUID同本地对象中的serialVersionUID进行对比,一致的话进行反序列化,不一致则失败报错(报InvalidCastException异常)

serialVersionUID的生成有三种方式(private static final long serialVersionUID= XXXL ):

  1. 显式声明:默认的1L
  2. 显式声明:根据包名、类名、继承关系、非私有的方法和属性以及参数、返回值等诸多因素计算出的64位的hash值
  3. 隐式声明:未显式的声明serialVersionUID时java序列化机制会根据Class自动生成一个serialVersionUID(最好不要这样,因为如果Class发生变化,自动生成的serialVersionUID可能会随之发生变化,导致匹配不上)

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

public class Employee implements java.io.Serializable {
     // 加入序列版本号
     private static final long serialVersionUID = 1L;
     public String name;
     public String address;
     // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
     public int eid; 

     public void addressCheck() {
         System.out.println("Address  check : " + name + " -- " + address);
     }
}

4 扩展

其实对于对象转化成json字符串和json字符串转化成对象,也是属于序列化和反序列化的范畴,相对于JDK提供的序列化机制,各有各的优缺点:

  • JDK序列化/反序列化:原生方法不依赖其他类库、但是不能跨平台使用、字节数较大
  • json序列化/反序列化:json字符串可读性高、可跨平台使用无语言限制、扩展性好、但是需要第三方类库、字节数较大

如果文章对您有帮助,希望点个赞/收藏/关注! O(∩_∩)O~

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值