【Java基础】序列化与反序列化深入分析

一、前言

  复习Java基础知识点的序列化与反序列化过程,整理了如下学习笔记。

二、为什么需要序列化与反序列化

  程序运行时,只要需要,对象可以一直存在,并且我们可以随时访问对象的一些状态信息,如果程序终止,那么对象是肯定不会存在的,但是有时候,我们需要再程序终止时保存对象的状态信息,之后程序再次运行时可以重新恢复到之前的状态,如,玩家玩游戏退出时,需要保存玩家的状态信息(如等级、装备等等),之后玩家再此登入时,必须要恢复这些状态信息。我们可以通过数据库手段来达到这个保存状态的目的,在Java中,我们有更简便的方法进行处理,那就是序列化与反序列化。序列化是一种对象持久化的手段,反序列化与序列化相反,其是通过序列化后的信息重新组装成对象。序列化与反序列化普遍应用在网络传输、RMI等场景中。

三、序列化概述

  3.1 序列化类结构图

  下面展示了与序列化相关的类的结构图

  说明:虚线框的表示接口类型,实线框表示具体的类。

  3.2 序列化关键字说明

  与序列化相关的关键字如下

  说明:

  1. 关键字transient,用来修饰字段,表示此字段在默认序列化过程中不会被处理,但是可以采用另外的手段进行处理。

  2. 关键字serialVersionUID,表示序列化版本号,当两个类的序列化ID一致时允许反序列化,默认可以采用编译器提供的值1L。

  3.3 序列化方法说明

  与序列化相关的方法如下

  说明:writeObject与readObject方法分别在ObjectOutput接口与ObjectInput接口中声明,在ObjectOutputStream与ObjectInputStream中实现。

四、Serializable

  4.1 Serializable定义

  Serializable定义如下  

public interface Serializable {
}

  说明:Serializable为一个接口,并且没有任何字段和方法,仅仅作为一个标识。

  4.2 使用说明

  当序列化对象时,只需要将对象标记为可序列化,即实现接口Serializable即可。下面的Person类实现了Serializable接口。 

 

  Person类的friend字段设置为transient,表明不会被序列化,定义完Person类之后,我们即可以对Person类进行序列化与反序列化操作了,具体代码如下 

 

  运行结果如下  

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
name = leesf, gender = man, age = 24, friend info is [null]

  说明:由于friend字段标记为transient,则默认序列化操作时不会进行序列化,反序列化后其值为null。

  4.3 问题说明

  1. Person类不实现Serializable接口

  若Person类不实现Serializable接口,进行序列化时,会发生什么,会出现如下异常。 

Exception in thread "main" java.io.NotSerializableException:****

  表示Person没有实现Serializable接口,具体原因如下

  在调用writeObject方法后,会经过一系列的调用,具体的调用栈如下

  说明:截取了writeObject0函数中的一段代码,可以看到会检查该对象是否是Serializable类型,不是,则会抛出异常。

  2. 处理transient对象

  当字段被transient修饰时,采用默认的序列化机制将不会对其进行处理,但是,如果要序列化transient字段时,如何做呢,可以在要进行序列化的类中添加writeObject和readObject方法,其方法签名如下 

private void writeObject(ObjectOutputStream stream) throws IOException
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException

  说明:注意,writeObject与readObject是采用private修饰符修饰的,说明,此方法只能在该类的其他方法中被调用,其他类中不能调用此方法,那么当调用ObjectOutputStream的writeObject方法时,如何调用到此方法来执行用户自定义处理逻辑的呢,答案是反射。利用反射可以在别的类中调用到此类中私有的方法,反射很强大。

  利用这个方法,我们修改Person类如下  

 

  测试类的代码不做修改,运行结果如下  

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]

  说明:在实现自定义的逻辑时,在writeObject方法中可以调用defaultWriteObject()方法实现默认序列化(序列化非transient字段),可以单独处理transient关键字;在readObject方法中可以调用defaultReadObject()方法实现默认反序列化,可以单独处理transient关键字(需要赋值)。值得注意的是,writeObject方法中defaultWriteObject和处理transient关键字的逻辑必须与readObject中defaultReadObject和处理transient关键字的逻辑顺序一致,否则会抛出异常。

  在调用writeObject方法后,会经过一系列的调用,具体的调用栈如下

  说明:经过反射,最终会调用到在Person类中定义的writeObject方法。readObject方法的调用可以以此类比,不再累赘。

五、Externalizable

  除了使用Serializable接口进行序列化以外,还可以使用Externalizable接口来进行序列化。

  5.1 Externalizable定义

  Externalizable的定义如下  

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

  说明:Externalizable实现了Serializable接口,并且添加了两个方法writeExternal与readExternal,需要序列化的类需要实现Externalizable接口,并且重写接口中定义的两个方法。

  5.2 使用说明

  首先将序列化的类实现Externalizable接口并且重写writeExternal与readExternal方法,并在这两个方法中实现处理逻辑。我们定义Person类如下

 

  说明:Person类实现了Externalizable接口,重写了writeExternal与readExternal方法,并且实现了用户自定义序列化与反序列化逻辑。测试类代码不变,运行结果如下: 

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]

  说明:从结果可知,成功进行了序列化与反序列化过程。值得注意的是,我们必须要给Person类提供一个无参构造器,才能正确完成序列化与反序列化过程。否则会抛出如下异常

  修改Person类如下

 

  说明:提供一个参数的构造函数,没有无参构造函数,修改测试类代码如下  

 

  运行结果如下

name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
Exception in thread "main" java.io.InvalidClassException: com.hust.grid.leesf.serializable.Person; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1775)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at com.hust.grid.leesf.serializable.SerializableDemo.main(SerializableDemo.java:32)

  说明:在反序列化的过程抛出了异常,可以看出是Person类没有合法的构造器,合法的构造器就是指无参构造器。当提供了无参构造器之后,就可以正确运行。

  5.3 问题说明

  1. Externalizable,writeObject与readObject方法

  如果Person类实现了Externalizable接口,并且在Person类中添加了writeObject与readObject方法,那么在进行序列化与反序列化时,是以哪种方法为准呢,修改Person类如下

 

  说明:在方法中添加了打印语句,这样就可以轻易判别采用的何种方式。测试类代码如下  

 

  运行结果

use writeExternal method
use writeExternal method
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]
use readExternal method
use readExternal method
name = leesf, gender = man, age = 24, friend info is [name = dyd, gender = woman, age = 24, friend info is [null]]

  说明:从结果可以看出,是以Externalizable接口中定义的两个方法进行序列化与反序列化的,这时,读者可能又会有另外一个疑问,那就是为什么会打印两次呢?答案是因为该方法被调用了两次,因为Person类有一个Person域,会导致调用两次。

  2. 处理transient字段

  可以在writeExternal与readExternal方法中实现自定义逻辑,对transient字段进行序列化与反序列化。

六、序列化问题

  6.1 采用默认序列化机制,类的静态字段会被序列化吗?

  采用默认序列化机制进行序列化时,类的静态字段会被序列化吗,此时类的静态字段不会被序列化,当然,我们可以采用自定义序列化逻辑对静态变量进行序列化。

  6.2 父类序列化问题

  采用默认序列化机制序列化子类时,其父类的字段会被序列化吗?可以分为如下情形

  1. 父类没有实现Serializable接口,没有提供默认构造函数

  这时,反序列化会出错,提示没有提供正确的构造函数。修改Person类,代码如下

 

  测试类的的代码如下 

 

  运行结果 

number = 1, name = leesf, gender = man, age = 24, friend info is [number = 2, name = dyd, gender = woman, age = 24, friend info is [null]]
Exception in thread "main" java.io.InvalidClassException: com.hust.grid.leesf.serializable.Person; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1775)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at com.hust.grid.leesf.serializable.SerializableDemo.main(SerializableDemo.java:32)

  说明:可以看出是没有提供合法的构造函数。

  2. 父类没有实现Serializable接口,提供默认构造函数

  第一步中出现了错误,此时,我们修改Person类,代码如下  

 

  说明:给Human类提供了无参构造函数。测试类代码不变,运行结果如下

number = 1, name = leesf, gender = man, age = 24, friend info is [number = 2, name = dyd, gender = woman, age = 24, friend info is [null]]
number = 0, name = leesf, gender = man, age = 24, friend info is [null]

  说明:此时,我们可以看到,可以进行反序列化了,但是父类的number字段被赋值为int的默认值0,Person类的transient字段没有被序列化。

  3. 父类实现Serializable接口

  当父类实现Serializable接口时,修改Person类代码如下 

 

  测试类的代码不变,运行结果如下 

number = 1, name = leesf, gender = man, age = 24, friend info is [number = 2, name = dyd, gender = woman, age = 24, friend info is [null]]
number = 1, name = leesf, gender = man, age = 24, friend info is [null]

  说明:从结果可知,已经可以进行正确的序列化与反序列化了,子类的transient字段没有被序列化。

  6.3 共享对象序列化问题  

  当序列化的两个对象都包含另外一个对象的引用时,在反序列化时,另外一个对象只会出现一次吗?修改Person类代码如下  

 

  测试类代码如下 

 

  运行结果如下  

[[email protected], [email protected], [email protected]]
[[email protected], [email protected], [email protected]]
[[email protected], [email protected], [email protected]]

  说明:从结果可知,oos1执行的writeObject是向同一个内存空间写了两次,从结果可看出,两次写入的对象的地址空间都是一样的,即进行了浅拷贝。而oos2执行的writeObject是向另外一个内存空间写了一次,从结果可看出,因为对象的地址不同于之前的对象地址,即采用了深拷贝。

七、总结

  写到这里,关于Java中的序列化与反序列化机制就已经分析完了,经过此次分析,对序列化机制的认识更加深刻。学习一个知识点,就要认认真真,踏踏实实的弄懂一个知识点,写博客就是一个特别好的方式。谢谢各位园友的观看~

参考链接

http://blog.csdn.net/jiangwei0910410003/article/details/18989711/

http://www.hollischuang.com/archives/1140

  

  

时间: 2024-08-07 16:38:57

【Java基础】序列化与反序列化深入分析的相关文章

Java的序列化与反序列化

Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本.图片.音频.视频等, 而这些数据都会以二进制序列的形式在网络上传送.那么当两个Java进程进行通信时,能否实现进程间的对象传送

Java对象序列化和反序列化

Java对象序列化和反序列化 在Java中,我们如果要保存一个对象的瞬时状态值,以便在下次使用时能够得到这些值,或者持久化对象,或者使用RMI(远程方法调用),或在网络中传递对象时,此时我们就需要将对象序列化,实现序列化,我们只要实现Serializable接口,该接口是一个标记接口(Tag interface),即里面没有方法,其主要作用就是告诉JVM该类的对象可以进行序列化. 一般来说,很多类的对象都实现了Serializable接口,但是,有些对象是不能进行序列化的,比如与数据库相关的连接

java 对象序列化与反序列化

Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为字节序列的过程: Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本.图片.音频.视频等, 而这些数据都会以二进制序列的形式在网络上传送.那么当两个Java进程进行通信时,能否实现进程间的

Java对象序列化与反序列化

Java对象序列化与反序列化 对象序列化的目标是将对象保存在磁盘中或者在网络中进行传输.实现的机制是允许将对象转为与平台无关的二进制流. java中对象的序列化机制是将允许对象转为字节序列.这些字节序列可以使Java对象脱离程序存在,从而可以保存在磁盘上,也可以在网络间传输. 对象的序列化是将一个Java对象写入IO流:与此对应的,反序列化则是从IO流中恢复一个Java对象. 实现序列化 如果要将一个java对象序列化,那么对象的类需要是可序列化的.要让类可序列化,那么这个类需要实现如下两个接口

深入分析Java的序列化与反序列化

阅读目录 Java对象的序列化如何对Java对象进行序列化与反序列化序列化及反序列化相关知识ArrayList的序列化ObjectOutputStream总结 序列化是一种对象持久化的手段.普遍应用在网络传输.RMI等场景中.本文通过分析ArrayList的序列化来介绍Java序列化的相关内容.主要涉及到以下几个问题: 怎么实现Java的序列化 为什么实现了java.io.Serializable接口才能被序列化 transient的作用是什么 怎么自定义序列化策略 自定义的序列化策略是如何被调

Java基础系列8:Java的序列化与反序列化

一 简介 把对象转换为字节序列的过程称为对象的序列化把字节序列恢复为对象的过程称为对象的反序列化对象的序列化主要有两种用途:1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中:2) 在网络上传送对象的字节序列. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,

【转】Java对象序列化和反序列化

[转自]孤傲苍狼的Java基础学习总结——Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中: 2) 在网络上传送对象的字节序列. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对 象,当有 10万用户并发访问,就有可

Java对象序列化与反序列化(1)

序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以被保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象.序列化机制使得对象可以脱离程序的运行而独立存在. 对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象.如果需要让某个对象可以支持序列化机制,必须让它的类是可序列化的(serializable),为了让某个类是可序列化的,该类必须实现如下两个接口之一: (1)Se

java 的序列化和反序列化的问题

引言 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream 进行对象的读写.然而在有些情况下,光知道这些还远远不够,文章列举了笔者遇到的一些真实情境,它们与 Java 序列化相关,通过分析情境出现的原因,使读者轻松牢记 Java 序列化中的一些高级认识. 回页首 文章结构 本