使用“持久性”

来源:互联网 发布:java基本数据类型字节 编辑:程序博客网 时间:2024/06/07 09:28
一个比较诱人的使用序列化技术的想法是:存储程序的一些状态,以便我们随后可以很容易
地将程序恢复到当前状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两
个都具有指向第三个对象的引用的对象进行序列化,会发生什么情况?当我们从它们的序列
化状态恢复这两个对象时,第三个对象会只出现一次吗?如果将这两个对象序列化成独立的
文件,然后在代码的不同部分对它们进行反序列化,又会怎样呢?


下面这个例子展示说明上述问题:


//: c12:MyWorld.java 


import java.io.*;
import java.util.*;


class House implements Serializable {}


class Animal implements Serializable {
  private String name;
  private House preferredHouse;
    Animal(String nm, House h) {
    name = nm;
    preferredHouse = h;
    } 
  public String toString() {
        return name + "[" + super.toString() +
      "], " + preferredHouse + "\n";
    } 
}


public class MyWorld { 
    public static void main(String[] args)
    throws IOException, ClassNotFoundException {
        House house = new House();
        List animals = new ArrayList();
        animals.add(new Animal("Bosco the dog", house));
        animals.add(new Animal("Ralph the hamster", house));
        animals.add(new Animal("Fronk the cat", house));
    System.out.println("animals: " + animals);
    ByteArrayOutputStream buf1 =
      new ByteArrayOutputStream();
        ObjectOutputStream o1 = new ObjectOutputStream(buf1);
    o1.writeObject(animals);
        o1.writeObject(animals); // Write a 2nd set
        // Write to a different stream:
    ByteArrayOutputStream buf2 =
      new ByteArrayOutputStream();
        ObjectOutputStream o2 = new ObjectOutputStream(buf2);
    o2.writeObject(animals);
    // Now get them back:
        ObjectInputStream in1 = new ObjectInputStream(
      new ByteArrayInputStream(buf1.toByteArray()));
        ObjectInputStream in2 = new ObjectInputStream(
      new ByteArrayInputStream(buf2.toByteArray()));
        List 
      animals1 = (List)in1.readObject(),
      animals2 = (List)in1.readObject(),
      animals3 = (List)in2.readObject();
    System.out.println("animals1: " + animals1);
    System.out.println("animals2: " + animals2);
    System.out.println("animals3: " + animals3);
    } 
} ///:~


这里有一件有趣的事:我们可以通过一个字节数组来使用对象序列化,从而实现对任何可
Serializable 对象的  “深度复制(deep copy)”(深度复制意味着我们复制的是整个对象
网,而不仅仅是基本对象及其引用)。复制对象将在附录 A 中进行深入地探讨。


在这个例子中,Animal 对象包含有类型为 House 的域。在 main()方法中,创建了一个
Animal 列表并将其两次序列化,分别送至不同的流。当其被反序列化并被打印时,我们可
以看到执行某次运行后的结果如下(每次运行时,对象将会处在不同的内存地址):


animals: [Bosco the dog[Animal@1cde100], House@16f0472
, Ralph the hamster[Animal@18d107f], House@16f0472
, Fronk the cat[Animal@360be0], House@16f0472
]
animals1: [Bosco the dog[Animal@e86da0], House@1754ad2
, Ralph the hamster[Animal@1833955], House@1754ad2
, Fronk the cat[Animal@291aff], House@1754ad2
]
animals2: [Bosco the dog[Animal@e86da0], House@1754ad2
, Ralph the hamster[Animal@1833955], House@1754ad2
, Fronk the cat[Animal@291aff], House@1754ad2
]
animals3: [Bosco the dog[Animal@ab95e6], House@fe64b9
, Ralph the hamster[Animal@186db54], House@fe64b9 
, Fronk the cat[Animal@a97b0b], House@fe64b9
]


当然我们期望这些被反序列化的对象地址与原来的地址不同。但请注意,在 animals1
animals2 中却出现了相同的地址,包括二者共享的那个指向 house 的引用。另一方面,
当恢复 animals3 时,系统无法知道另一个流内的对象是第一个流内对象的别名,因此它会
产生出完全不同的对象网。
只要我们将任何对象序列化到一个单一流中,我们就可以恢复出与我们写出时一样的对象
网,并且没有任何意外重复复制出的对象。当然,我们可以在写出第一个对象和写出最后一
个对象期间改变这些对象的状态,但是这是我们自己的责任;无论在对象被序列化时处于什
么状态(无论它们和其他对象有什么样的连接关系),它们都可以被写出。


如果我们想保存系统状态,最安全的作法是将其作为“原子”操作进行序列化。如果我们序列
化了某些东西,再去做其他一些工作,再来序列化更多的东西,如此等等,那么我们将无法
安全地保存系统状态。取而代之的是,将构成系统状态的所有对象都置入某个单一容器内,
并在一个操作中将该容器直接写出。然后同样只需一次方法调用,即可以将其恢复。


下面这个例子是一个想象的计算机辅助设计(CAD)系统,用来演示这一方法。此外,它
还引入了 static 域的问题;如果我们查看 JDK 文档,就会发现 Class 是“Serializable”的,
因此只需直接对 Class 对象序列化,就可以很容易地保存 static 域。在任何情况下,这都
是一种明智的做法。


//: c12:CADState.java
// Saving and restoring the state of a pretend CAD system.
// {Clean: CADState.out}
//package c12;
import java.io.*;
import java.util.*;


abstract class Shape implements Serializable {
    public static final int RED = 1, BLUE = 2, GREEN = 3; 
    private int xPos, yPos, dimension;
    private static Random r = new Random();
    private static int counter = 0;
    public abstract void setColor(int newColor);
    public abstract int getColor();
    public Shape(int xVal, int yVal, int dim) {
    xPos = xVal;
    yPos = yVal;
    dimension = dim;
    } 
  public String toString() {
    return getClass() +
            "color[" + getColor() + "] xPos[" + xPos +
            "] yPos[" + yPos + "] dim[" + dimension + "]\n"; 
    } 
    public static Shape randomFactory() {
        int xVal = r.nextInt(100); 
        int yVal = r.nextInt(100); 
        int dim = r.nextInt(100); 
    switch(counter++ % 3) {
            default: 
      case 0: return new Circle(xVal, yVal, dim);
      case 1: return new Square(xVal, yVal, dim);
            case 2: return new Line(xVal, yVal, dim);
        } 
    } 
}


class Circle extends Shape {
    private static int color = RED;
    public Circle(int xVal, int yVal, int dim) {
    super(xVal, yVal, dim);
    } 
    public void setColor(int newColor) { color = newColor; }
    public int getColor() { return color; }
}


class Square extends Shape {
    private static int color;
    public Square(int xVal, int yVal, int dim) { 
    super(xVal, yVal, dim);
    color = RED;
    } 
    public void setColor(int newColor) { color = newColor; }
    public int getColor() { return color; }
}


class Line extends Shape {
    private static int color = RED;
  public static void
  serializeStaticState(ObjectOutputStream os)
    throws IOException { os.writeInt(color); }
  public static void
  deserializeStaticState(ObjectInputStream os)
    throws IOException { color = os.readInt(); }
    public Line(int xVal, int yVal, int dim) {
    super(xVal, yVal, dim);
    } 
    public void setColor(int newColor) { color = newColor; }
    public int getColor() { return color; }
}


public class CADState {
    public static void main(String[] args) throws Exception {
    List shapeTypes, shapes;
    if(args.length == 0) {
      shapeTypes = new ArrayList();
            shapes = new ArrayList(); 
            // Add references to the class objects:
            shapeTypes.add(Circle.class); 
            shapeTypes.add(Square.class); 
            shapeTypes.add(Line.class); 
      // Make some shapes:
            for(int i = 0; i < 10; i++)
        shapes.add(Shape.randomFactory());
            // Set all the static colors to GREEN:
            for(int i = 0; i < 10; i++)
        ((Shape)shapes.get(i)).setColor(Shape.GREEN);
      // Save the state vector:
      ObjectOutputStream out = new ObjectOutputStream(
        new FileOutputStream("CADState.out"));
      out.writeObject(shapeTypes);
      Line.serializeStaticState(out);
      out.writeObject(shapes);
        } else { // There's a command-line argument
      ObjectInputStream in = new ObjectInputStream(
                new FileInputStream(args[0])); 
            // Read in the same order they were written:
      shapeTypes = (List)in.readObject();
      Line.deserializeStaticState(in);
      shapes = (List)in.readObject();
        } 
    // Display the shapes:
    System.out.println(shapes);
    } 
} ///:~
Shape 类实现了 Serializable,所以任何自 Shape 继承的类也都会自动地是 Serializable。
每个 Shape 都含有数据,而且每个衍生自 Shape 的类都包含一个 static 域,用来确定所
有那些 Shape 类型的颜色(如果将 static 域置入基类,只会产生一个域,因为 static 域不
能在衍生类中复制)。可对基类中的方法进行重载,以便为不同的类型设置颜色(static
方法不会动态绑定,所以这些都是普通的方法)。每次调用 randomFactory()方法时,它
都会使用不同的随机数作为 Shape 的数据,从而创建不同的 Shape。
Circle 和 Square 是 Shape 的直接扩展;唯一的差别是 Circle 是在定义时初始化 color
的,而 Square 是在构造器中初始化 color 的。我们将 Line 留到稍后再讨论。
在 main()中,一个 ArrayList 用于容纳 Class 对象,而另一个用于容纳几何形状。如果我
们不提供命令行参数,就会创建 shapeTypes ArrayList,并添加 Class 对象。然后创建
shapes ArrayList,并添加 Shape 对象。接下来,所有的 static color 值都会被设置成
GREEN,而且所有东西都会序列化到文件 CADState.out 中。
如果我们供了一个命令行参数(假设为 CADState.out),便会打开那个文件,并用它恢
复程序的状态。无论是哪种情况,作为结果产生的 Shape 的 ArrayList 都会被打印出来。
某次运行的结果如下:


$ java CADState
[class Circlecolor[3] xPos[71] yPos[82] dim[44]
, class Squarecolor[3] xPos[98] yPos[21] dim[49]
, class Linecolor[3] xPos[16] yPos[80] dim[37]
, class Circlecolor[3] xPos[51] yPos[74] dim[7]
, class Squarecolor[3] xPos[7] yPos[78] dim[98]
, class Linecolor[3] xPos[38] yPos[79] dim[93]
, class Circlecolor[3] xPos[84] yPos[12] dim[62]
, class Squarecolor[3] xPos[16] yPos[51] dim[94]
, class Linecolor[3] xPos[51] yPos[0] dim[73]
, class Circlecolor[3] xPos[47] yPos[6] dim[49]
]


我们可以看到,xPos,yPos 以及 dim 的值都被成功地保存和恢复了,但是对 static 信息
的读取却出现了问题。所有读回的颜色都是“3”,但是真实情况却并非如此。Circle 的值为
1(定义为 RED),而 Square 的值为 0(记住,它们是在构造器中被初始化的)。看上去
似乎 static 的域根本没有被序列化!确实如此——尽管 Class 类是 Serializable 的,但它
却不能按我们所期望的去运行。所以假如想序列化 static 值,你必须自己动手去实现。


这正是 Line 中的 serializeStaticState()和 deserializeStaticState()两个 static 方法的
用途。我们可以看到,它们是作为存储和读取过程的一部分被显式地调用的。(注意必须维
护写入序列化文件和从该文件中读回的顺序)。因此,为了使 CADState.java 正确运转起
来,我们必须这样:


1. 为几何形状添加 serializeStaticState()和 deserializeStaticState()。
2. 移除 ArrayList shapeTypes 以及与之有关的所有代码。
3. 在几何形状内添加对新的序列化和反序列化静态方法的调用。


另一个要注意的问题是安全,因为序列化也会将 private 数据保存下来。如果我们有安全问
题,那么应将其标记成 transient。但是这之后,我们还必须要设计一种安全的保存信息的

方法,以便在执行恢复时,我们可以复位那些 private 变量。


原创粉丝点击