序列化和反序列化学习笔记1

序列化

将数据结构或者对象转换为二进制串的过程。

反序列化

将序列化后的二进制串转换成数据结构或者对象。

数据序列化就是将数据结构或者对象转换成我们可以存储或者传输的数据格式的一个过程。在序列化的过程中,数据结构或者对象将其状态信息写入到临时或者持久性的存储区中,而在对应的反序列化中,则可以说是生成的数据被还原成数据结构或者对象的过程。

序列化的目的

  • 数据持久化,永久的保存数据
  • 序列化后进行网络传输,因为网络传输都是以字节流的方式进行传输的
  • 将对象数据在进程之间进行传输
  • Java平台允许我们在内存中创建可复用的对象,但一般情况下,只有当JVM处于运行中时,这些对象才可能存在,即这些对象的生命周期不会比JVM的生命周期长,但是在现实应用中,停止JVM时,可能需要保存对象的状态,以便下次启动时能够恢复被保存的对象,这就需要序列化和反序列化实现
  • 序列化对象的时候只针对变量进行序列化,不会针对方法进行序列化
  • 在Intent之间,基本的数据类型可进行直接传递,但是复杂的数据类型,需要进行序列化操作

序列化和反序列化协议

  • XML & SAOP
    XML是一种常用的序列化和反序列化协议,具有跨机器、跨语言等优点,SOAP是一种被广泛应用的,基于XML的结构化消息传递协议

  • JSON
    起源于弱类型语言JavaScript,是目前使用非常广泛的一种序列化协议,相比XML,序列化后的数据更加简洁,解析速度也更快,并且具有人眼可读的优点

  • ProtoBuf
    具备了优秀的序列化协议的所需的众多典型特征。标准的IDL和IDL编译器,对工程师非常友好,序列化后的数据非常简洁,仅为XML的1/3到1/10。解析速度非常快,是XML的20-100倍。提供了非常友好的动态库,使用非常简单。

使用Serializable序列化

需要序列化的类

public class Student implements Serializable {
    private String name;
    private String address;
    private int age;


    public Student(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }

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

测试类

    public static void main(String args[]) throws IOException, ClassNotFoundException {
        //序列化过程
        Student student = new Student("lixiaojin","this is address",18);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("student.txt"));
        objectOutputStream.writeObject(student);
        objectOutputStream.close();

        //反序列化过程
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("student.txt"));
        Student student1 = (Student)inputStream.readObject();
        System.out.println(student1.toString());

    }

输出为:

Student{name='lixiaojin', address='this is address', age=18}

需要注意的问题:

  • serialVersionUID
    如果没有定义该变量,序列化后如果修改了该类,在用之前的序列化数据反序列化时会报错。比如在Student类中添加score变量,然后用student.txt执行反序列化操作,会报错。
    private int score;

仅执行一下语句

        //反序列化过程
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("student.txt"));
        Student student1 = (Student)inputStream.readObject();
        System.out.println(student1.toString());

会出现一下错误

Exception in thread "main" java.io.InvalidClassException: serializable.Student; local class incompatible: stream classdesc serialVersionUID = 5572876292036517038, local class serialVersionUID = 3911773034578579717
    at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
    at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1958)
    at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1827)
    at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2115)
    at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646)
    at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
    at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
    at serializable.StudentTest.main(StudentTest.java:15)

解决方法是在Student类中添加serialVersionUID变量。该变量用来辅助序列化和反序列化过程,该变量值会被写入到序列化后的文件中,原则上只有当文件中的值和当前类中的值相同时,才会正常的被反序列化,修改后的Student类如下所示

public class Student implements Serializable {
    private static final long serialVersionUID = 5572876292036517038L;
    private String name;
    private String address;
    private int age;
    private int score;
    
    public Student(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }

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

如果定义了serialVersionUID变量,在序列化后又添加了新的变量score,在反序列化时score的值会被初始化为0,即对应变量类型的默认值

  • 因为静态变量不属于对象,所以不会被初始化,因为序列化保存的是对象的状态
  • 用transient标记的变量不会被序列化
    如果用transient变量修饰address,在反序列化后,可以发现address的值为空,表明,用该关键词修饰后,不会被序列化
private transient String address;

从序列化文件中也可以看出

//这是添加transient关键字之后的序列化文件
���sr�serializable.StudentMV�v�\���I�ageI�scoreL�namet�Ljava/lang/String;xp�tthis is name
//这是添加transient关键字之前的序列化文件
���sr�serializable.StudentMV�v�\���I�ageI�scoreL�addresst�Ljava/lang/String;L�nameq~�xp�t�this is addresstthis is name

可以明显的看出添加transient关键字之后,是没有address相关的字段信息的

  • 如果有引用变量,引用变量也需要实现Serializable接口
    如下所示,定义Child类,在Student中进行初始化,在反序列化时因为Child没有实现Serializable接口,所以不能被顺利反序列化。
public class Child {
    public boolean happy;

    @Override
    public String toString() {
        return "Child{" +
                "happy=" + happy +
                '}';
    }
}
 private Child child;
    public Student(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
        child = new Child();
        child.happy = true;
    }
Exception in thread "main" java.io.NotSerializableException: serializable.Child
    at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)
    at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
    at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
    at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
    at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
    at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
    at serializable.StudentTest.main(StudentTest.java:10)
  • 如果被序列化的类的父类没有实现Serializable 接口,在反序列化时会出错
    新的Student类和Child类如下所示
public class Student extends Child implements Serializable {
    private static final long serialVersionUID = 5572876292036517038L;
    private String name;
    private String address;
    private int age;
    private int score;
    
    public Student(String name, String address, int age) {
        super(true);
        this.name = name;
        this.address = address;
        this.age = age;

    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                ", score=" + score +
                ", happy=" + happy +
                '}';
    }
}
public class Child {
    public boolean happy;

    public Child(boolean happy){
        this.happy = happy;
    }

    @Override
    public String toString() {
        return "Child{" +
                "happy=" + happy +
                '}';
    }
}

在进行反序列化的过程中,会出现如下问题

Exception in thread "main" java.io.InvalidClassException: serializable.Student; no valid constructor
    at java.base/java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:159)
    at java.base/java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:864)
    at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2116)
    at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646)
    at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
    at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
    at serializable.StudentTest.main(StudentTest.java:15)

原因是,在反序列化时,初始化Student时,会先初始化父类,但是父类没有实现Serializable接口,所以无法初始化,但是如果为Child类定义一个无参的构造函数,不会出现该错误,但是Child 中的值都为默认值。
为Child添加无参构造函数之后的运行结果

Student{name='this is name', address='this is address', age=18, score=0, happy=false}

可以看到,在序列化之前,为happy赋值为true,但是反序列化之后,happy的值为false,表明Child未进行正常的反序列化操作,仅仅是使用无参构造函数进行了初始化而已。

  • 单例问题,如果一个单例被序列化后,再被反序列化,会生成新的对象,违反对象唯一。单例模式在反射应用中存在同样的问题。在序列化问题中,解决单例问题的方式是重写readResolve方法。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容