Java序列化Serializable小结

来源:互联网 发布:济宁网络问政平台育才 编辑:程序博客网 时间:2024/05/16 15:48

一、什么是序列化和反序列化

        序列化是一种将对象的状态信息转换为可以存储或传输的形式的过程,通俗一点讲就是将对象转化为字节序列的过程;

        反序列化则序列化的反过程,即把字节序列恢复为对象的过程。

二、什么情况下需要序列化

        在很多应用中,需要对象进行序列化,让它们离开内存空间、入驻物理硬盘中,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存就吃不消了,于是Web容器就会把一些seesion先序列化到硬盘中,等使用的时候再把保存在硬盘中的对象还原到内存中;同样两个远程进程通信时,无论双方发送的是什么类型的数据,最终都将以二进制的形式在网络上传送(发送方需要序列化为字节序列,接收方需要反序列化恢复Java对象)。因此总结如下:

(1)把内存中的对象保存到文件中或者数据库中的时候;

(2)利用套接字在网络上传送对象的时候;

(3)通过RMI(远程方法调用)传输对象的时候;        

三、如何实现序列化

        java.io.Serializable只有实现了Serializable将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记表明该类可以序列化。

        java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

        java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它反序列化为一个对象,并返回。

        总结序列化步骤:1)创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;2)通过对象输出流的writeObject()方法写对象;

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

四、序列化和反序列化例子

        在序列化对象的时候,首先创建OutputStream(如FileOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中,这时候只需要调用writeObject()方法就可以将对象序列化,并将其发送到outputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构);在反序列化的时候,需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。

        下面创建了一个SerializableTest类,有name和age两个成员变量、serializableTest()序列化和deserializableTest()反序列化函数。

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;public class SerializableTest implements Serializable {private static final long serialVersionUID = 1L;//序列化IDprivate String name;private int age;public SerializableTest(){this("Jack", 24);}public SerializableTest(String name, int age){this.name = name;this.age = age;}public static void serializableTest(){try{//序列化ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.me"));SerializableTest myTest = new SerializableTest();oos.writeObject(myTest);oos.flush(); //缓冲流oos.close(); //关闭流}catch(FileNotFoundException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}}public static void deserializableTest(){//反序列化ObjectInputStream oin = null;try{oin = new ObjectInputStream(new FileInputStream("test.me"));}catch(FileNotFoundException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}SerializableTest st = null;try{st = (SerializableTest)oin.readObject();//由object对象向下转型为SerializableTest}catch(ClassNotFoundException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}System.out.println("Name : " + st.name);System.out.println("Age : " + st.age);}public static void main(String[] args) {serializableTest();//序列化deserializableTest();//反序列化}}

        

        关于序列化ID(serialVersionUID ),字面意思是序列化的版本号凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量private static final long serialVersionUID,实现Serializable接口的类如果类中没有添加serialVersionUID,那么就会出现警告提示,在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。这也可能是造成序列化和反序列化失败的原因,因为不同的序列化id之间不能进行序列化和反序列化。

        serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:
    1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
    2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

五、序列化前后的对象关系

       序列化前的对象与序列化后的对象之间是"=="还是"equal"(也就是深复制还是浅复制的问题)呢?

        因为序列化前后对象的地址不同了、而内容却是一样的,对象中包含的引用也相同。换句话说,通过序列化操作后,我们实现的是对对象的“深度复制(deep copy)”过程,即我们复制的是整个对象网,而不仅仅时基本对象及其引用。对于同一流对象,他们的地址相同,说明他们是同一对象,但是与其他流的对象地址却是不同的,也就是说只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中对象都是同一个。

六、静态变量能否序列化

        回答是否定的。因为静态变量属于类级别的,序列化会忽略静态变量,即序列化不保存静态变量的状态------静态成员属于类级别的,所以不能序列化,即序列化的是对象的状态不是类的状态。这里的不能序列化的意思,是序列化信息中不包含这个静态成员域。最上面添加了static后之所以还是输出24是因为该值是JVM加载该类时分配的值。注:transient后的变量也不能序列化,但是情况稍复杂。

七、总结

1)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
2)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
3) static,transient后的变量不能被序列化;


0 0
原创粉丝点击