序列化与反序列化

来源:互联网 发布:java servlet destroy 编辑:程序博客网 时间:2024/06/04 00:42

1.1 对象序列化简介

对象序列化的目标是将对象保存到磁盘中,或允许在网路中直接传输对象。对象序列化机制允许把 Java对象转换成平台无关的二进制流,从而允许把二进制流永久的保存在磁盘上,通过网路把这种二进制流保存到另一个网络节点。比如在Web应用中需要保存到HtppSessionServletContext属性的Java对象。

序列化是针对类的对象的一个过程,某个类的对象能否序列化和反序列化,就看该类是否实现接口java.io.Serializable

1.2 序列化

当一个类实现了Serializable接口时,表明这个类的对象就是可序列化的,程序可以通过两个步骤来序列化该对象:

l   创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其它节点流的基础之上。

l   调用ObjectOutputStream对象的writeObject方法输出该序列化对象。

 

1.2.1 定义类

定义一个Person类,该Person类是一个普通的Java,只是实现了Serializable接口,该接口标识该类的对象是可序列化的。

package com.test;import java.io.Serializable;public class Person implements Serializable{/** * 序列化标识 */private static final long serialVersionUID = 4123256053288461134L;private String name;private int age;public Person(String name, int age) {System.out.println("有参数的构造器");this.name = name;this.age = age;}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;}}


 

1.1.1 序列化类的对象

package com.test;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class WriteObject {public static void main(String[] args) {ObjectOutputStream oos = null;try {// 创建一个ObjectOutputStream输出流oos = new ObjectOutputStream(new FileOutputStream("object.txt"));//创建一个类对象Person person = new Person("孙悟空", 500);// 将Person对象写入输入流oos.writeObject(person);}catch (Exception e) {e.printStackTrace();}finally {if (oos != null) {try {oos.close();}catch (IOException e) {e.printStackTrace();}}}}}


 

注意:在上面的程序中,注意在最后将程序的IO流关闭。运行程序之后,刷新一下项目,会在项目的根目录下面出现一个object.txt文件的,打开文件之后你可以看到是一堆乱码,那是一些二进制文件,所以人眼是看不出来滴,接下面就通过程序代码来读取这堆乱码中的东西吧。

1.1 反序列化

如何从二进制流中恢复Java对象,也就是我们所说的反序列化:

l   创建一个ObjectInputStream,这个输出流也是一个处理流,所以也必须建立在其它节点流的基础之上。

调用ObjectInputStream对象的readObject方法读取流中对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型。

package com.test;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class ReadObject {public static void main(String[] args) {ObjectInputStream ois = null;try{// 创建一个ObjectInputStream 输出流ois = new ObjectInputStream(new FileInputStream("object.txt"));// 读取出Person对象Person p = (Person) ois.readObject();System.out.println("名字为:" + p.getName() + "\n年龄为:" + p.getAge());}catch (Exception e) {e.printStackTrace();}finally {if (ois != null) {try {ois.close();}catch (IOException e) {e.printStackTrace();}}}}}


 

1.1 对象引用的序列化

如果某个类的属性类型不是基本类型或是String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的属性类是不可序列化的。

例如:Teacher类持有一个对Person类的引用,则只有当Person类是可序列化的,Teacher类才是序列化的,否则无论Teacher无论实现Serializable接口,都是不可序列化的。

1.2 serialVersionUID

1.2.1 JDK的解释

根据“JDK API 1.6.0中文版”有:

序列化运行时使用一个称为 serialVersionUID的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID"的字段(该字段必须是静态 (static)、最终 (final) long 型字段)显式声明其自己的 serialVersionUID

 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

 

如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID值,如“Java(TM) 对象序列化规范中所述。不过,强烈建议所有可序列化类都显式声明 serialVersionUID值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID值的要求。

1.2.2 自己的理解

l   首先,每个类继承接口java.io.Serializable后,生成的serialVersionUID的值各不同的。再次,反序列化时,比较对象的类的 serialVersionUID,与序列化的对象的类,的版本号是否相同。

l   简单的说就是:serialVersionUID是用来唯一标识序列化的对象的类。serialVersionUID和类是一一对应。serialVersionUID能唯一标识类,类的serialVersionUID也只有一个。

1.2.3 代码的体验

                                步骤 1     创建工程

将序列化和反序列化两部分的三个类(PersonWriteObjectReadObject)放到一个工程中,保存。

                                步骤 2     运行类WriteObject

运行类WriteObject,在工程的根目录下将生成object.txt文件,该文件里面保存的是二进制数据。

                                步骤 3     运行类ReadObject

运行类ReadObject,该类将工程根目录下的object.txt文件中的二进制对象读取出来,并打印到控制台,如下:

名字为:孙悟空

年龄为:500

                                步骤 4     修改Person类的serialVersionUID

原来的值:private static final long serialVersionUID = 4123256053288461134L;

修改后为:private static final long serialVersionUID = 4123257053288461134L;

保存。

                                步骤 5     再次运行ReadObject

再次运行ReadObject,控制台报错:

java.io.InvalidClassException: com.test.Person; local class incompatible: stream classdesc serialVersionUID = 4123256053288461134, local class serialVersionUID = 4123257053288461134

       at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)

       at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)

       at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)

       at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)

       at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)

       at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)

       at com.test.ReadObject.main(ReadObject.java:18)

                                步骤 6     去掉serialVersionUID

去掉Person中的serialVersionUID后,再次运行ReadObject,能成功运行。说明,继承接口Serializable后,会有默认的serialVersionUID值。

原创粉丝点击