精华区 [关闭][返回]

当前位置:网易精华区>>讨论区精华>>编程开发>>● Java>>J2EE>>技 术 FAQ-对象序列化(转自清华bbs)

主题:技 术 FAQ-对象序列化(转自清华bbs)
发信人: yat(Dr.Jone)
整理人: zjxyz(2001-11-27 08:37:56), 站内信件
清华发文id:vagrant (一切率性而安,但求心之所安)


技 术 FAQ
对象序列化
为了能够向ObjectOutputStream流中写入对象,该对象类必须标志为
Serializable,这是为什么? 
JDK1.1中哪些系统类被标记为serializable? 
当我将AWT组件deserialize时出现问题,我如何能使其正常工作? 
对象序列化支持加密吗? 
对象序列化类是针对数据流进行操作的,我如何将一个对象写到一个随机存取文件
中? 
当一个本地对象是序列化的并且被RMI调用做为参数传递,该本地对象方法的字节
码是否也可以被传递?当远程虚拟机保持该对象的句柄时,对象的一致性是怎样的?
 
我如何通过ObjectOutputStream对象创建一个ObjectInputStream对象,而且中间
不需要文件。 
我创建了一个对象并在网络上用writeObject方法发送,用readObject方法接收。
如果我改变该对象中一个参数的值,并象过去那样发送接收时,发现readObject方
法接收到的是原来的对象,没有体现出参数数值的改变。这是为什么? 
有支持可序列化线程对象的计划吗? 
我可以计算diff(serial(x),serial(y))吗? 
可不可以用我自己的zip和unzip方法压缩我的对象的序列化表示? 
在我的对象的压缩版本上能否运行方法调用,例如isempty(zip(serial(x)))? 
如果我尝试去序列化一个字体或图形对象,并且试着在另一个虚拟机中重建该对象
,我的应用程序死掉了。为什么? 
我如何对一个对象树进行序列化? 
如果类A没有实现Serializable,但它的一个子类B实现了Serializable,类A中的
变量会因为类B序列化而序列化吗? 
1.为了能够向ObjectOutputStream流中写入对象,该对象类必须标志为
Serializable,这是为什么? 
答案:我们并不是随随便便要求该对象类实现java.io.Serializable接口的,这样
设计的目的是为了在开发者需求和系统需求之间求得一个平衡以提供一个可以预测
的安全机制。最困难的设计限制是要满足JAVA编程语言中类的安全要求。     

    如果类对象需要被标明是可序列化的,开发组担心某个开发人员会由于遗忘,
懒惰,或者是粗心等方面的原因没有将其标明为是可序列化的,从而使该类对象对
于RMI是不可用的,甚至失去保留的意义。我们担心这样的要求会使开发者需要付
出很大的精力去了解别人以后会如何使用该类对象,而这本来是不可事先知道的情
况。事实上,正如alpha API表现的那样,我们的初始设计是要求类对象缺省条件
下是可序列化的。在经过有关安全性和正确性的考虑后,我们修改了原来的设计,
因为我们确信类对象的缺省值只能是不可序列化的。 


安全性的限制 
    促使我们改变缺省设置的首要因素是出于安全的考虑,尤其是考虑到那些用
private, package protected, 或 protected定义保护的私有区域,在程序运行
期间,JAVA平台限制对那些区域的读写操作从而提供必要的安全保护。     

    可是,上述的读写保护限制对一个可序列化的对象不起作用,对象序列化产生
的数据流可以被任何对该数据流有访问权限的对象读取或修改。允许任何对象存取
可序列化对象就违背了用户在安全方面的期望。并且在流中的数据可以被任意更改
,这就有可能创建出在JAVA平台的保护下从来不许创建出的对象来,这不仅是用户
数据和程序的安全性保护要求没有满足,JAVA平台本身运行的稳定性安全性也受到
极大威胁。

    这样的错误是没有办法避免的,因为序列化的目的就是允许一个对象被转换成
一种可以在JAVA平台外传输的形式(从而脱离了平台在安全性和完整性方面提供的
保护)。要求对象被定义成可序列化的就意味着类的设计者必须做出有背安全性和
完整性原则的决定。一个不知道序列化的开发者由于缺少相关的知识不能接受有关
安全性的开发工作。我们希望当开发人员将类对象定义为可序列化时,曾经仔细考
虑了这样定义有可能产生的后果。 需要说明的是,此类安全问题不是通过一个安
全管理程序机制就能够解决的。由于序列化本身就是允许一个对象在虚拟机之间传
送(或者经过一段空间,如在RMI中;或者经过一段时间,比如数据流被保存到一个
文件中),所使用的安全机制必须独立于任何JAVA虚拟机。我们要尽可能避免出现
对象在一个虚拟机中可以序列化而在另一个虚拟机中不能序列化的问题。由于安全
管理程序是虚拟机运行环境的一部分,用它来解决序列化的安全问题是不行的。 



做出明智的判断 
    虽然安全问题是我们改变缺省设置的首要因素,我们认为另一个原因同样重要
的地方是只能序列化那些在设计阶段就进行了周密考虑的类。设计一个在序列化后
重新构造从而导致崩溃的类对象实在是太容易了。通过要求类的设计者声明实现
Serializable接口,我们希望设计者也能考虑一下序列化该类可能导致的结果。

    很容易找到相应的例子。许多类对象管理着那些仅在对象运行时起作用的信息
,比如文件句柄,开放socket连接,安全信息等等。这些数据可以通过关键字
transient将其定义为临时变量,但是那样的定义方式仅仅在对象将被序列化时才
是必需的。一个新手(或者是粗心的)程序员可能忘记做这样的标识,同样程序员也
可能忘记将类标识为支持serializable的。这种情况一般不会导致错误运行,只要
避免不要去序列化那些没有标识 serializable的对象。

    另外一个例子是关于这样一个对象的,该对象位于某个RMI引用图的根部,向
下扩展出许多的其它对象。序列化这样的对象会导致许多其它对象的序列化,因为
序列化对整个引用图都起作用。如此做时必须考虑周全,不能随便就使用缺省设定
。 

    在定义JAVA API类库中的系统类为可序列化时(这是恰当的决定),我们考虑过
各种需求。我们本来认为那是非常简单的过程,就是大多数系统类被定义为可序列
化从而仅使用缺省设定不需要做任何更改。实际上我们发现远非原来所想。在大多
数类中,必须仔细考虑诸如是否要将变量定义为transient以及类序列化后对变量
是否有影响等等问题。 

    当然,没有办法保证程序员或类制定者在标记一个类是可序列化时确实考虑到
了这些问题。然而,当程序员定义某一个类实现了serializable接口,我们确实要
求程序员做出了一些考虑。让序列化是一个对象的缺省设置意味着考虑不周,可能
会对程序产生坏的影响,而这正是JAVA平台设计过程中竭力避免的。 

2.JDK1.1中哪些系统类被标记为serializable? 

答案:下面列出了被标记为serializable的类:(这些类的子类也都是可序列化的


java.lang.Character
java.lang.Boolean
java.lang.String
java.lang.StringBuffer
java.lang.Throwable-(包括所有Exception的子类)
java.lang.Number-(包括Integer、Long等)
java.util.Hashtable
java.util.Random
java.util.Vector-(包括Stack)
java.util.Date
java.util.BitSet
java.io.File
java.net.InetAddress
java.rmi.server.RemoteObject

AWT类

基本数据类型数组

对象数组是可序列化的(尽管对象有可能不是可序列化的)3.当我将AWT组件
deserialize时出现问题,我如何能使其正常工作? 
答案:当你序列化AWT构件时,也同时序列化了那些将AWT功能模块与本地window建
立对应关系的peer对象。当你deserialize AWT构件时,那些原来的peer对象被重
新构造,但它们已经失效了。对于本地window系统来讲peer对象是本地的,并且在
本地地址空间拥有指向数据结构的指针,所以不能被移动。

    为解决这一问题,你首先要把最上层的构件从它的容器中取走(从而构件不再
是"活动"的了)。这样Peer对象被丢弃,你可以仅保存AWT构件的状态。当你以后将
构件读回来并进行deserialize时,将顶层构件加入到窗口以使AWT构件可见。你可
能还需要增加一个show调用。 
    在JDK1.1以及更新的版本中,AWT构件是可序列化的。 java.awt.Component类
实现可序列化。但它们不可以与JDK1.0.2的构件互操作。 

4.对象序列化支持加密吗? 

答案:对象序列化本身不拥有任何加密/解密机制。它读写标准的JAVA API数据流
,所以它可以和任何加密技术同时工作。对象序列化可以用在许多不同的领域。不
仅仅是对文件进行读写,它还可被RMI用来在主机间建立连接。当RMI使用序列化时
,加密解密工作被交给更低的网络传输层。我们希望采用诸如SSL或类似的方式得
到一个安全的网络连接。 

5.对象序列化类是针对数据流进行操作的,我如何将一个对象写到一个随机存取文
件中? 

答案:当前还没有直接的方式将对象写入随机存取文件。你可以使用
ByteArrayInputStream 和ByteArrayOutputStream作为读写随机存取文件的中转站
,并通过数据流创建 ObjectInputStreams和ObjectOutputStreams对象来完成对象
的传输工作。你只需保证在数据流中包含对象全部即可,否则对该对象的读写操作
会导致失败。 

    例如,可以用java.io.ByteArrayOutputStream接收来自ObjectOutputStream
的数据。通过它你会得到一个数组形式的结果。同样,可以用
ByteArrayInputStream对象做为ObjectInputstream的输入。 

6.当一个本地对象是序列化的并且被RMI调用做为参数传递,该本地对象方法的字
节码是否也可以被传递?当远程虚拟机保持该对象的句柄时,对象的一致性是怎样
的? 

答案:一个本地对象的方法代码并不在ObjectOutputStream中直接传送,但是如果
该对象的类不能在本地找到时,接收端要进行类装载。类文件本身没有序列化,序
列化的只是类的名字。在deserialization过程中,所有类都应当可以被正常的类
装载机制来载入。对于applet这意味着它们被AppletClassLoader装入。 对于传递
给远程虚拟机的本地对象不会有一致性保证,因为那样的对象是通过拷贝自身内容
的方式传递的(真正的按值传递方式)。 

7.我如何通过ObjectOutputStream对象创建一个ObjectInputStream对象,而且中
间不需要文件。 

答案: ObjectOutputStream 和 ObjectInputStream都是针对数据流对象工作的。
你可以使用ByteArrayOutputStream获得一个数组,并将该数组传递给
ByteArrayInputStream。你也可使用piped数据流类。任何由OutputStream和
InputStream类扩展得到的java.io类都可以使用。 

8.我创建了一个对象并在网络上用writeObject方法发送,用readObject方法接收
。如果我改变该对象中一个参数的值,并象过去那样发送接收时,发现
readObject方法接收到的是原来的对象,没有体现出参数数值的改变。这是为什么


答案: ObjectOutputStream类跟踪它序列化了的每一个对象,并且如果该对象又
一次出现,就只发送它的句柄。这是它处理对象引用图的方式。相应的
ObjectInputStream跟踪它创建的所有对象以及它们的句柄,从而当句柄再一次出
现时,它返回同一个对象。输入和输出数据流一直保持这种状态,直到它们被释放
为止。 

    另外, ObjectOutputStream类可以实现一个reset方法,在发出一个对象后清
空内存,从而又一次发送对象将产生新的拷贝。 

9.有支持可序列化线程对象的计划吗? 

答案:线程是不能序列化的。在当前的实现方式中,如果你试着去序列化接着并行
化(deserialize)一个线程,正常情况下不可能获得新的线程和堆栈,因为没有办
法为对象分配系统资源。简单地讲这样不可能正常工作,并且可能导致不可预测的
后果。 

    序列化线程的困难是因为它们的运行状态与虚拟机复杂地绑接在一起,几乎不
可能重新建立。例如,如果有本地方法调用了C过程,从而又去调用了JAVA平台中
的代码,导致JAVA编程语言结构与C语言指针管理之间令人难以置信的复杂组合,
这时仅保存虚拟机调用堆栈是不够的。并且对堆栈序列化也会影响到通过堆栈变量
可以到达的对象的序列化。 

    如果有一个线程(序列化后又并行化)在同一个虚拟机中被再次调度,它将和
原始线程共享许多相同的状态参数,正如两个C线程要去共享一个堆栈一样,这两
个线程如果都要立刻运行,将以不可预测的方式失败。当在不同的虚拟机中要并行
化时,几乎不可能判断出会发生什么样的事情。 

10.我可以计算diff(serial(x),serial(y))吗? 

答案:每当同一个对象序列化时,通过diff将产生出相同的流。为序列化每一个对
象,你都应该创建一个新的ObjectOutputStream。 

11.可不可以用我自己的zip和unzip方法压缩我的对象的序列化表示? 

答案: ObjectOutputStream创建一个输出流OutputStream;如果你的zip对象扩展
了OutputStream类,那么压缩它不成任何问题。 

12.在我的对象的压缩版本上能否运行方法调用,例如
isempty(zip(serial(x)))? 

答案:不是对所有对象都能运行方法调用,主要是由于对象编码问题。对于一个特
殊对象(例如一个字符串),你可以进行位流比较,因为压缩编码始终都是稳定的,
每次你对同一个对象进行压缩编码,都会得到相同的位流结果。 

13.如果我尝试去序列化一个字体或图形对象,并且试着在另一个虚拟机中重建该
对象,我的应用程序死掉了。为什么? 

答案:AWT的序列化并不很完善,因此在你传送字体或图象时会有问题。这是因为
每一个对象持有的内存指针都只在原始的虚拟机中有效,而当它传给新的虚拟机时
就会产生段错误。

    目前字体已经可以被序列化,而图象序列化将会在今后的JDK版本中解决。 

14.我如何对一个对象树进行序列化? 

答案:接下来是一个显示如何序列化对象树的简单例子。 

import java.io.*;

class tree implements java.io.Serializable {

    public tree left;
    public tree right;
    public int id;
    public int level;
    private static int count = 0;

    public tree(int depth) {
        id = count++;
        level = depth;
        if (depth > 0) {
            left = new tree(depth-1);
            right = new tree(depth-1);
        }
    }


    public void print(int levels) {
        for (int i = 0; i < level; i++)
System.out.print(" ");
System.out.println("node " + id);
if (level <= levels && left != null)
left.print(levels);
if (level <= levels && right != null)
right.print(levels);
}

public static void main (String argv[]) {
try {
/* Create a file to write the serialized tree to。 */
FileOutputStream ostream = new FileOutputStream("tree.
tmp");
/* Create the output stream */
ObjectOutputStream p = new ObjectOutputStream(ostream);
/* Create a tree with three levels。 */
tree base = new tree(3);
p.writeObject(base); // Write the tree to the stream。
p.flush();
ostream.close(); // close the file。
/* Open the file and set to read objects from it。 */
FileInputStream istream = new FileInputStream("tree.tmp");
ObjectInputStream q = new ObjectInputStream(istream);
/* Read a tree object, and all the subtrees */
tree new_tree = (tree)q.readObject();
new_tree.print(3); // Print out the top 3 levels of the
tree
} catch (Exception ex) {
ex.printStackTrace();
}
}
}15.如果类A没有实现Serializable,但它的一个子类B实现了Serializable,类A
中的变量会因为类B序列化而序列化吗?

答案:只有可序列化的对象所包含的变量可以被写出和复原。仅当拥有一个无参数
构造函数从而初始化那些非可序列化的超类变量时,该该对象才是可以被复原的。
如果子类有存取超类状态的权限,它就能够实现writeObject和readObject方法从
而保存并复原那些状态。





----

jaguar:美洲豹
       靓跑车
       Sybase 的J2EE Server
       每次打的回宿舍的时候供辨认的一个到达标志    

[关闭][返回]