对象的序列化与反序列化

来源:互联网 发布:matlab2016 mac破解版 编辑:程序博客网 时间:2024/06/06 03:32
       把对象转换为字节序列的过程称为对象的序列化。
  把字节序列恢复为对象的过程称为对象的反序列化

  对象的序列化主要有两种用途:
       1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
              你可以序列化其他东西,包括数组,基本数据类型等等

  2) 在网络上传送对象的字节序列。

  在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

  当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

      一个类若想被序列化,则需要实现java.io.Serializable  接口,该接口中没有定义任何方法,是一个标识性接口(Marker Interface),当一个类实现了该接口,就表示这个类的对象是可以序列化的。

      在序列化时,static 变量是无法序列化的;如果A 包含了对B 的引用,那么在序列化A的时候也会将B一并地序列化;如果此时A 可以序列化,B 无法序列化,那么当序列化A  的时候就会发生异常,这时就需要将对B 的引用设为transient ,该关键字表示变量不会被序列化。

      当我们在一个待序列化/反序列化的类中实现了以上两个 private  方法(方法声明要与上面的保持完全的一致),那么就允许我们以更加底层、更加细粒度的方式控制序列化/反序列化的过程(参考最后一个例子)。


二、JDK类库中的序列化API

  java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
  java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
  对象序列化包括如下步骤:
  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
  2) 通过对象输出流的writeObject()方法写对象。

  对象反序列化的步骤如下:
  1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
  2) 通过对象输入流的readObject()方法读取对象。

序列化:
package io;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;public class IOTest7{public static void main(String[] args) throws IOException{String path = "c:" + File.separator + "a.txt";Person p1 = new Person("zhangsan", 12);Person p2 = new Person("lisi", 14);// 此处创建文件写入流的引用是要给ObjectOutputStream的构造函数玩儿FileOutputStream fos = null;ObjectOutputStream oos = null;try{fos = new FileOutputStream(path);oos = new ObjectOutputStream(fos);// 这里可以写入对象,也可以写入其他类型数据oos.writeObject(p1);oos.writeObject(p2);}catch (IOException e){e.printStackTrace();}finally{try{oos.close();}catch (IOException e){e.printStackTrace();}}}}// 一个类要想实现序列化则必须实现Serializable接口class Person implements Serializable{private static final long serialVersionUID = 591235087569242999L;private String name;private int age;public Person(String name, int age){this.name = name;this.age = age;}public String toString(){return "Name:" + this.name + ", Age:" + this.age;}}


反序列化:

public class IOTest7

{public static void main(String[] args) throws IOException{String path = "c:" + File.separator + "a.txt";// 好吧,这里代码写得着实有点长了,还要抛异常什么的// 如果你也看的烦,那就在主方法上抛吧,构造方法里用匿名对象就好了// 什么?别告诉我你不知道匿名对象FileInputStream fis = null;ObjectInputStream ois = null;try{fis = new FileInputStream(path);ois = new ObjectInputStream(fis);// 这里返回的其实是一个Object类对象// 因为我们已知它是个Person类对象// 所以,就地把它给向下转型了Person p = (Person) ois.readObject();System.out.println(p);// 抛死你,烦烦烦~!!!}catch (IOException e){e.printStackTrace();}catch (ClassNotFoundException e){e.printStackTrace();}finally{try{// 还是要记得关闭下流ois.close();}catch (IOException e){e.printStackTrace();}}}}

三、serialVersionUID的作用

s​e​r​i​a​l​V​e​r​s​i​o​n​U​I​D​:​ ​字​面​意​思​上​是​序​列​化​的​版​本​号​,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量

扯了那么多,那么serialVersionUID(序列化版本号)到底有什么用呢,下面我们修改一下Person类,添加多一个sex属性,如下:

// 一个类要想实现序列化则必须实现Serializable接口class Person implements Serializable{//private static final long serialVersionUID = 591235087569242999L;private String name;private int age;private String sex;public Person(String name, int age, String sex){this.name = name;this.age = age;this.sex = sex;}@Overridepublic String toString(){return "Person [age=" + age + ", name=" + name + ", sex=" + sex + "]";}}

然后执行反序列操作,此时就会抛出如下的异常信息:

1 Exception in thread "main" java.io.InvalidClassException: Person; 2 local class incompatible: 3 stream classdesc serialVersionUID = -88175599799432325, 4 local class serialVersionUID = -5182532647273106745

  意思就是说,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。在TestSerialversionUID例子中,没有指定Customer类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。  

四、serialVersionUID的取值

  serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
  类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值

  显式地定义serialVersionUID有两种用途:
    1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
    2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

五、自己控制序列化和反序列化的过程

package Decorator;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;/** * 如果需要特出处理 需要实现writeObject和readObject  * 这两个方法不属于任何一个类和任何一个接口 是非常特殊的方法 * 让自己控制序列化和反序列化的过程 *  */public class SerializableTest2 {public static void main(String[] args) throws Exception {Person2 Person21 = new Person2("zhangsan", 21, 56.34);Person2 Person22 = new Person2("lisi", 24, 23.42);Person2 Person23 = new Person2("wangwu", 65, 67.45);/** * 序列化 把信息保存到磁盘上 */try {FileOutputStream fos = new FileOutputStream("Person2.txt");ObjectOutputStream ooStream = new ObjectOutputStream(fos);ooStream.writeObject(Person21);ooStream.writeObject(Person22);ooStream.writeObject(Person23);System.out.println("write success");ooStream.close();System.out.println("===================");/** * 把文件中内容读取到内存中 反序列化时不会调用对象的任何构造方法,仅仅根据所保存的对象状态信息 在内存中重新构建对象 */FileInputStream fis = new FileInputStream("Person2.txt");ObjectInputStream ois = new ObjectInputStream(fis);try {Person2 p = null;for (int i = 0; i < 3; i++) {p = (Person2) ois.readObject();System.out.println("第" + Integer.valueOf(i + 1) + "个" + ":"+ p.getName() + "," + p.getAge() + ","+ p.getHeight() + ";");}} catch (ClassNotFoundException e) {e.printStackTrace();}ois.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}class Person2 implements Serializable {private static final long serialVersionUID = 2605116530076131408L;// 加上transient 关键字 该属性将不会被保存private String name;private int age;private double height;public Person2(String name, int age, double height) {super();this.name = name;this.age = age;this.height = height;}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 double getHeight() {return height;}public void setHeight(double height) {this.height = height;}/** * 如果需要特殊处理 需要实现这两个方法 *  * @param out * @throws IOException */private void writeObject(ObjectOutputStream out) throws IOException {System.out.println("write Object...");}private void readObject(ObjectInputStream in) throws IOException {System.out.println("read Object...");}}

输出:
read Object...
第1个:null,0,0.0;
read Object...
第2个:null,0,0.0;
read Object...
第3个:null,0,0.0;


0 0