Java 序列化

来源:互联网 发布:查看淘宝店铺排名 编辑:程序博客网 时间:2024/06/07 07:22

  • 序列化和反序列化
  • 为什么要序列化
  • 需要序列化的情况
  • 不能序列化的变量类型
  • 如何实现序列化和反序列化
    • 要求
    • 实现序列化
    • 序列化ID
    • 序列化前和序列化后的对象关系
  • 补充
  • 参考

序列化和反序列化

Serialization(序列化)是将对象转换为字节(byte)流(一组byte,即一个byte[])的过程;deserialization(反序列化)是将字节流重建为对象的过程。

为什么要序列化

这个问题在网上看了一些回答,理解的还比较粗浅,假设你想要将一个对象从一台机器上通过网络传输到另一台机器上,由于网络只能传输字节流,不能传输对象,故需要将该对象在一台机器上进行序列化,通过网络传输到另一台机器上后再反序列化,这样才能完成该对象的传输。
可参考 java为什么要序列化 里的各种回答。个人比较喜欢关于移大厦的那个比喻,形象且易懂。

需要序列化的情况

  • 当你想把对象保存在磁盘的文件中或者数据库中的时候
  • 当你想用套接字在网络上传送对象的时候
  • 当你想用远程方法调用(Remote Method Invocation, 简称RMI)传输对象的时候

不能序列化的变量类型

  1. static变量(静态变量)是不能序列化的,因为java序列化保存的是对象的状态而不是类的状态,而static变量保存在全局数据区,在对象未实例化时就已经生成,属于类的状态。
  2. transient变量(这个关键字的作用就是告诉java 它不可以被序列化),java的serialization提供了一种持久化对象实例的机制,当持久化对象实例时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient关键字。transient是java的关键字,用来表示一个域不是该对象串行化(序列化)的一部分。

如何实现序列化和反序列化

要求

  • 对象要想序列化,则必须实现Serializable接口
  • 序列化时要用ObjectOutputStream实现
  • 反序列化时要用ObjectInputStream实现

Serializable接口是一个标记接口(tagging interface),没有任何方法,实现该接口只是起到一个指示或标记的作用,这是javac或java规定的(类似于Cloneable接口),说明该类的对象可以序列化,而没有实现这个接口的类的对象则不可以序列化。

实现序列化

要想序列化对象,必须先创建一个OutputStream(如FileOutputStream, ByteArrayOutputStream),然后把它(封装在)嵌进一个ObjectOutputStream,这时就能用writeObject()方法把对象写入OutputStream。
例如:

package com.test;import java.io.*;public class SerialTest implements Serializable{    private static final long serialVersionUID = 1L;//序列化ID,见下部分详解    private String name = "huo";    private int age = 24;    public static void main(String[] args){        try{            FileOutputStream myfile = new FileOutputStream("mySerialFile.txt");//存放对象的文件            ObjectOutputStream oos = new ObjectOutputStream(myfile);            SerialTest myTestObj = new SerialTest();            oos.writeObject(myTestObj);            oos.flush();//缓冲流,可注释掉            oos.close();//关闭流        }        catch(FileNotFoundException e){            e.printStackTrace();        }        catch(IOEception e){            e.printStackTrace();        }        deserialize();//调用下面的 反序列化 函数    }    public static void deserialize(){//反序列化过程        ObjectInputStream ois = null;//局部变量必须初始化        try{            ois = new ObjectInputStream(new FileInputStream("mySerialFlie.txt"));        }        catch(FileNotFoundException e1){            e1.printStackTrace();        }        catch(IOException e1){            e1.printStackTrace();        }        SerialTest mytest = null;        try{            mytest = (SerialTest)ois.readObject();//由Object对象向下转型为SerialTest对象            System.out.println("name= " + mytest.name);            System.out.println("age= " + mytest.age);            ois.close();        }         catch(ClassNotFoundException e){            e.printStackTrace();        }        catch(IOException e){            e.printStackTrace();        }    }}

执行上面的代码后会在此项目的工作空间生成一个mySerialFile.txt的文件。反序列化的输出为:
name= huo
age = 24

序列化ID

反序列化要根据serialVersionUID还原对象,不同的序列化ID之间不能进行序列化和反序列化。
序列化id决定着是否能够成功反序列化。java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本体实体类中的serialVersionUID进行比较,如果相同则认为版本是一致的,可以进行反序列化,否则会报序列化版本不一致的异常。

当我们一个实体类中没有显示的定义一个名为“serialVersionUID”、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。那么如何解决呢?便是在本地类中添加一个“serialVersionUID”变量,值保持不变,便可以进行序列化和反序列化。
序列化ID在Eclipse下有两种生成策略:一种是固定的1L,另一种是随机生成一个不重复的long类型的数据(实际由jdk工具生成),建议没有特殊需求时就使用默认的1L,可以确保代码一致的情况下反序列化成功。

jdk文档关于serialVersionUID的描述:

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

详解参考 java 序列化ID的作用
Java序列化的几种方式以及序列化的作用
Java 序列化Serializable详解(附详细例子)

序列化前和序列化后的对象关系

深复制,反序列化还原后的对象地址与原来的的地址不同,同时复制了整个对象网。
参考 Java 序列化Serializable详解(附详细例子)
以及 Java中的深拷贝(深复制)和浅拷贝(浅复制)

补充

  • 当一个父类实现序列化,子类自动实现序列化,不需要显示地实现Serializable接口
  • 当一个对象的实例变量引用了其他对象,序列化该对象时也将引用对象进行序列化(即深复制)

参考

本篇文章主要参考了以下文章内容:

  1. Java 序列化Serializable详解(附详细例子)
  2. Java序列化的几种方式以及序列化的作用
  3. Java序列化与static
  4. java 序列化ID的作用
  5. Java中equals和==的区别
  6. Java中的深拷贝(深复制)和浅拷贝(浅复制)
0 0