【老白学 Java】对象序列化
对象序列化
文章来源:《Head First Java》修炼感悟。
前几篇文章主要讨论了 GUI 方面的一些内容,如果对游戏设计情有独钟的话,可以仔细研究一下 API 文档。 今天我们要讨论的是文件读写方面的相关操作,比如如何写入序列化对象,如何恢复序列化对象等等。
一、什么是对象序列化
假如你正在对某个闯关类游戏发起挑战,每次失败后就要从新开始,数十次后你是否会放弃? 假如可以保存当前所有状态,从失败处恢复,你会不会愿意去做更多尝试? 以上情形主要涉及到了文件存取问题。
我们知道,对象有行为和状态两种属性。 上述情景中从失败处重新恢复,实际上就是恢复对象某一时刻的状态,前提是已经保存过对象状态。 主要有两种方式用来保存状态:
- 对象序列化并写入文件中,需要时再读取文件反序列化恢复对象;
- 写入纯文本文件,方便其它程序解析。
对象序列化也被形象的称作「扁平化」处理,就好比抽空充气娃娃时的样子没了支撑,但还保留着基本特征;而反序列化则是重新充气的操作,可以完好地恢复原状。
序列化对象写入文件后,哪怕你想修改点什么也无从下手,一般人很难阅读理解,因为它是专门用于类加载器的。 所以说对象序列化相对比较安全,并且比纯文本文件更容易恢复。
二、把序列化对象写入文件中
将对象写入文件可分为四个基本步骤:
2.1、创建文件输出流 FileOutputStream
你可以把 FileOutputStream
对象理解为用来输送字节的管道,一端被连接到了文件上准备写入,另一端连接上游序列化对象,暂时还没连接。
FileOutputStream fs = new FileOutputStream("Save.ser");
2.2、创建对象输出流 ObjectOutputStream
ObjectOutputStream
可以理解为对象连接下游文件流的管道。
ObjectOutputStream os = new ObjectOutputStresam(fs);
2.3、写入序列化数据
两段管道都连接好了,开始写入数据,字节会源源不断地流进文件中。
os.writeObject(states);
2.4、关闭对象输出流
最后,关闭对象输出流,相当于关闭上游管道阀门。
os.close();
结果上面的处理,states
对象就会写入 Save.ser
文件中,结束写入过程。
三、保存对象状态
对象状态,即对象实例变量的值,可以是基本数据类型,也可以是引用数据类型。 基本数据类型只要保存变量值就可以,引用类型变量要如何处理呢? 如果引用类型变量又带有其他的引用类型变量呢?
实际情况就是,对象序列化操作时,与之关联的所有引用变量都会被序列化。 这个过程就像树根一样向外延伸,只要被波及到都会被执行这个操作。 过程看起来相当复杂,但你也没必要过分担心,因为这一切都是由虚拟机处理的,你只要坐享其成就可以了。
实现 Serializable 接口
如果想要序列化一个对象,这个对象就必须实现 Serializable 接口。 该接口没有定义任何方法,只作为一个序列化标识,就是说,实现了Serializable 接口即表示该类可以被序列化。
可以想象一下,当你上线后发现强10武器变成了没有任何属性的白武器,你会不会抓狂? 所以必须确保序列化完整,否则就会出现残缺不全的数据结构,必定会出乱子。 Serializable 接口就是为此制定的强制遵守的协议,你不遵守就不允许序列化。
下面请看一个例子:
import java.io.*;
// 演示如何进行对象的序列化操作
public class Swordsman implements Serializable {private Weapon weapon = new Weapon();public static void main(String[] args) {Swordsman sm = new Swordsman();try {FileOutputStream fs = new FileOutputStream("swordsman.ser");ObjectOutputStream os = new ObjectOutputStream(fs);os.writeObject(sm);os.close();} catch (Exception e) {e.printStackTrace();}}
}
// 该类没有实现 Serializable 接口
class Weapon {// TODO:
}
编译、运行结果:
上面代码可通过编译,但运行时抛出了序列化异常。 原因就是序列化 Swordsman 实例对象时,同时也会序列化 Weapon 的实例变量,而 Weapon 并没有实现 Serializble 接口。
解决这个问题也很简单,Weapon 实现 Serializable 接口即可。 此时类文件目录中应该多出了一个 swordsman.ser 文件,内容如下:
使用 transient 忽略序列化
假如你不想让某个实例变量被序列化,或者该变量本就不应该被序列化,可以使用 transient
关键字修饰,从而忽略该变量的序列化。 比如:
// 使用关键字 transient 忽略序列化操作
public class Swordsman implements Serializable {String userName = "Gioliye"; // 可以被序列化transient Weapon weapon = new Weapon(); // 不会被序列化public static void main(String[] args) {// TODO:}
}
// 并没有实现 Serializable 接口
class Weapon {// TODO:
}
内容预告
下一篇文章再来了解一下反序列化操作,尝试从文件中恢复对象到原始状态。
《 上一篇 Box 布局管理器 | 下一篇 对象反序列化 》 |
---|