对象序列化

来源:互联网 发布:java scanner用法int 编辑:程序博客网 时间:2024/06/07 04:42
Java 的对象序列化(Object Serialization)将那些实现了 Serializable 接口的对象转换
成一个字节序列,并可以在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通
过网络进行。这意味着序列化机制能自动弥补不同操作系统之间的差异。也就是说,可以在
运行 Windows 系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行
Unix 系统的计算机,然后在那里准确地重新组装,而你却不必担心数据在不同机器上的表
示会不同,也不必关心字节的顺序或者其他任何细节。


就其本身来说,对象的序列化是非常有趣的,因为利用它可以实现“轻量级持久化
(lightweight persistence)”。“持久化”意味着一个对象的生存周期并不取决于程序是否
正在执行;它可以生存于程序的调用之间。通过将一个序列化对象写入磁盘,然后在重新调
用程序时恢复该对象,就能够实现持久化的效果。之所以称其为“轻量级”,是因为不能用某
种“persistent”(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽
管将来有可能实现)。相反,对象必须在程序中显式地序列化和重组。如果需要一个更严格
的持久化机制,可以考虑使用 Java 数据对象(JDO)或者像 Hibernate 之类的工具
(http://hibernate.sourceforge.net)。更多的细节可参考《Thinking in Enterprise
Java》,可从 www.BruceEckel.com 下载。
对象序列化的概念加入到语言中是为了提供对两种主要特性的支持。Java 的“远程方法调
用”(RMI,Remote Method Invocation)使存活于其他计算机上的对象使用起来就像是
存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
在《Thinking in Enterprise Java》中有对 RMI 的具体讨论。


对 Java Beans 来说对象的序列化也是必需的,可参看第 14 章。使用一个 Bean 时,一般
情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动
以后,进行恢复;具体工作由对象序列化完成。
只要对象实现了 Serializable 接口(该接口仅是一个标记接口,不包括任何方法),对象的
序列化处理就会非常简单。当序列化的概念被加入到语言中时,许多标准库类都发生了改变,
以便能够使之序列化——其中包括所有原始数据类型的封装器、所有容器类以及许多其他的
东西。甚至 Class 对象也可以被序列化。
为了序列化一个对象,首先要创建某些 OutputStream 对象,然后将其封装在一个
ObjectOutputStream 对象内。这时,只需调用 writeObject()即可将对象序列化,并将
其发送给 OutputStream。要将一个序列重组为一个对象,需要将一个 InputStream 封
装在 ObjectInputStream 内,然后调用 readObject()。和往常一样,我们最后获得的是
指向一个向上转型为 Object 的句柄,所以必须向下转型,以便能够直接对其进行设置。
对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内包含
的所有引用并保存那些对象;接着又能对每个这样的对象内包含的引用进行追踪;以此类推。
这种情况有时被称为“对象网”,单个对象可与之建立连接,而且它还包含了对象的引用数组
以和成员对象。如果必须保持一套自己的对象序列化机制,那么维护那些可追踪到所有链接
的代码可能会显得非常麻烦。然而,由于 Java 的对象序列化似乎找不出什么缺点,所以请
尽量不要自己动手,让它用优化的算法自动维护整个对象网。下面这个例子通过对链接的对
象生成一个“Worm”(蠕虫)对序列化机制进行了测试。每个对象都与 Worm 中的下一段
链接,同时又与属于不同类(Data)的对象引用数组链接:


//: c12:Worm.java
// Demonstrates object serialization.
// {Clean: worm.out}
import java.io.*;
import java.util.*;
class Data implements Serializable {
    private int n; 
    public Data(int n) { this.n = n; }
    public String toString() { return Integer.toString(n); }
}


public class Worm implements Serializable {
    private static Random rand = new Random();
    private Data[] d = {
    new Data(rand.nextInt(10)),
    new Data(rand.nextInt(10)),
    new Data(rand.nextInt(10))
    }; 
    private Worm next; 
  private char c;
    // Value of i == number of segments
    public Worm(int i, char x) {
        System.out.println("Worm constructor: " + i);
        c = x; 
    if(--i > 0)
            next = new Worm(i, (char)(x + 1));
    } 
  public Worm() {
    System.out.println("Default constructor");
    } 
  public String toString() {
        String s = ":" + c + "(";
        for(int i = 0; i < d.length; i++)
            s += d[i]; 
    s += ")";
    if(next != null)
            s += next; 
    return s;
    } 
    // Throw exceptions to console:
    public static void main(String[] args)
    throws ClassNotFoundException, IOException {
        Worm w = new Worm(6, 'a');
        System.out.println("w = " + w);
        ObjectOutputStream out = new ObjectOutputStream(
      new FileOutputStream("worm.out"));
    out.writeObject("Worm storage\n");
    out.writeObject(w);
        out.close(); // Also flushes output
    ObjectInputStream in = new ObjectInputStream(
      new FileInputStream("worm.out"));
    String s = (String)in.readObject();
    Worm w2 = (Worm)in.readObject();
        System.out.println(s + "w2 = " + w2);
    ByteArrayOutputStream bout =
      new ByteArrayOutputStream();
    ObjectOutputStream out2 = new ObjectOutputStream(bout);
    out2.writeObject("Worm storage\n");
    out2.writeObject(w);
    out2.flush();
        ObjectInputStream in2 = new ObjectInputStream(
      new ByteArrayInputStream(bout.toByteArray()));
    s = (String)in2.readObject();
    Worm w3 = (Worm)in2.readObject();
        System.out.println(s + "w3 = " + w3);
    } 
} ///:~
更有趣的是,Worm 内的 Data 对象数组是用随机数初始化的(这样就不用怀疑编译器保
留了某种原始信息)。每个 Worm 段都用一个 Char 标记。该 Char 是在递归生成链接的
Worm 列表时自动产生的。要创建一个 Worm,必须告诉构造器你所希望的它的长度。在
产生下一个引用时,要调用 Worm 构造器,并将长度减 1,以此类推。最后一个 next 句柄
则为 null(空),表示已到达 Worm 的尾部。


以上这些操作都使得事情变得更加复杂,从而加大了对象序列化的难度。然而,真正的序列
化过程却是非常简单的。一旦从另外某个流创建了 ObjectOutputStream,writeObject()
就会将对象序列化。注意也可以为一个 String 调用 writeObject()。也可以用与
DataOutputStream 相同的方法写入所有原始数据类型(它们具有同样的接口)。


有两个独立的代码段看起来是相似的。一个读写的是文件,而另一个读写的是一个字节数组
(ByteArray)。可利用序列化将对象读写到任何 DataInputStream 或者
DataOutputStream,甚至包括网络;正如在《Thinking in Enterprise Java》中所述。
某一次运行后的输出结果如下:


Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(414):b(276):c(773):d(870):e(210):f(279)
Worm storage
w2 = :a(414):b(276):c(773):d(870):e(210):f(279)
Worm storage
w3 = :a(414):b(276):c(773):d(870):e(210):f(279)


可以看出,重组的对象确实包含了原对象中的所有链接。


注意在对一个可序列化(Serializable)对象进行重组的过程中,没有调用任何构造器,包
括缺省的构造器。整个对象都是通过从 InputStream 中取得数据恢复而来的。

对象序列化是面向字节的,因此采用 InputStream 和 OutputStream 层次结构。