- ObjectOutputStream类
- ObjectOutputStream.PutField类
- writeObject方法
- writeExternal方法
- writeReplace方法
- useProtocolVersion方法
2.1 ObjectOutputStream类
类ObjectOutputStream
实现对象序列化。它维护流的状态,包括已经序列化的对象集合。其方法控制要序列化的对象的遍历,以保存指定的对象及其引用的对象。
package java.io;
public class ObjectOutputStream
extends OutputStream
implements ObjectOutput, ObjectStreamConstants
{
public ObjectOutputStream(OutputStream out)
throws IOException;
public final void writeObject(Object obj)
throws IOException;
public void writeUnshared(Object obj)
throws IOException;
public void defaultWriteObject()
throws IOException, NotActiveException;
public PutField putFields()
throws IOException;
public void writeFields()
throws IOException;
public void reset() throws IOException;
protected void annotateClass(Class<?> cl) throws IOException;
protected void annotateProxyClass(Class<?> cl) throws IOException;
protected void writeClassDescriptor(ObjectStreamClass desc)
throws IOException;
protected Object replaceObject(Object obj) throws IOException;
protected boolean enableReplaceObject(boolean enable)
throws SecurityException;
protected void writeStreamHeader() throws IOException;
public void write(int data) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;
public void flush() throws IOException;
protected void drain() throws IOException;
public void close() throws IOException;
public void writeBoolean(boolean data) throws IOException;
public void writeByte(int data) throws IOException;
public void writeShort(int data) throws IOException;
public void writeChar(int data) throws IOException;
public void writeInt(int data) throws IOException;
public void writeLong(long data) throws IOException;
public void writeFloat(float data) throws IOException;
public void writeDouble(double data) throws IOException;
public void writeBytes(String data) throws IOException;
public void writeChars(String data) throws IOException;
public void writeUTF(String data) throws IOException;
// Inner class to provide access to serializable fields.
public abstract static class PutField
{
public void put(String name, boolean value)
throws IllegalArgumentException;
public void put(String name, char data)
throws IllegalArgumentException;
public void put(String name, byte data)
throws IllegalArgumentException;
public void put(String name, short data)
throws IllegalArgumentException;
public void put(String name, int data)
throws IllegalArgumentException;
public void put(String name, long data)
throws IllegalArgumentException;
public void put(String name, float data)
throws IllegalArgumentException;
public void put(String name, double data)
throws IllegalArgumentException;
public void put(String name, Object data)
throws IllegalArgumentException;
}
public void useProtocolVersion(int version) throws IOException;
protected ObjectOutputStream()
throws IOException;
protected writeObjectOverride()
throws NotActiveException, IOException;
}
单参数ObjectOutputStream
构造函数创建一个将对象序列化到给定OutputStream
的ObjectOutputStream
。构造函数调用writeStreamHeader
方法向流中写入一个魔数和版本,该魔数和版本将由单参数ObjectInputStream
构造函数中的对应调用readStreamHeader
来读取和验证。如果安装了安全管理器,则此构造函数在直接或间接被覆盖putFields
和/或writeUnshared
方法的子类的构造函数调用时,会检查"enableSubclassImplementation"
SerializablePermission
。
writeObject
方法用于将对象序列化到流中。对象的序列化如下:
-
如果子类正在覆盖实现,则调用
writeObjectOverride
方法并返回。覆盖实现在本节末尾有描述。 -
如果块数据缓冲区中有数据,则将数据写入流中并重置缓冲区。
-
如果对象为null,则将null放入流中,
writeObject
返回。 -
如果对象已被替换,如步骤8中描述的,将替换的句柄写入流中,
writeObject
返回。 -
如果对象已经写入流中,则将其句柄写入流中,
writeObject
返回。 -
如果对象为
Class
,则将相应的ObjectStreamClass
写入流中,为该类分配一个句柄,writeObject
返回。 -
如果对象为
ObjectStreamClass
,则为对象分配一个句柄,然后使用writeClassDescriptor
方法之一将其写入流中,这些方法在Java 2 SDK标准版1.3及更高版本中被调用,如果它表示的类不是动态代理类,则通过将关联的Class
对象传递给java.lang.reflect.Proxy
的isProxyClass
方法来确定。然后,为所表示的类编写注释:如果类是动态代理类,则调用annotateProxyClass
方法;否则,调用annotateClass
方法。然后writeObject
方法返回。 -
通过对象的类和/或
ObjectInputStream
的子类进行潜在替换处理。-
如果对象的类不是枚举类型并定义了适当的
writeReplace
方法,则调用该方法。可选地,它可以返回要序列化的替代对象。 -
然后,通过调用
enableReplaceObject
方法启用,调用replaceObject
方法允许ObjectOutputStream
的子类替换要序列化的对象。如果原始对象在上一步中被替换,则使用替换对象调用replaceObject
方法。
如果原始对象被上述一步或两步替换,将原始对象到替换对象的映射记录下来,以便在步骤4中稍后使用。然后,重复步骤3到7处理新对象。
如果替换对象不是步骤3到7覆盖的类型之一,则在步骤10中使用替换对象恢复处理。
-
-
如果对象为
java.lang.String
,则将字符串作为长度信息写入,后跟使用修改后的UTF-8编码的字符串内容。有关详细信息,请参阅第6.2节,“流元素”。为字符串分配一个句柄,writeObject
返回。 -
如果对象为数组,则递归调用
writeObject
以写入数组的ObjectStreamClass
。为数组分配一个句柄。然后写入数组的长度。然后将数组的每个元素写入流中,之后writeObject
返回。 -
如果对象为枚举常量,则通过递归调用
writeObject
写入常量的枚举类型的ObjectStreamClass
。它只会在第一次引用时出现在流中。为枚举常量分配一个句柄。接下来,通过枚举常量的name
方法返回的值作为String
对象写入,如第9步所述。请注意,如果相同的名称字符串先前出现在流中,则将写入对其的反向引用。然后writeObject
方法返回。 -
如果对象为记录对象,则通过递归调用
writeObject
写入记录对象的类的ObjectStreamClass
。它只会在第一次引用时出现在流中。为记录对象分配一个句柄。记录对象的组件写入流中。
a. 如果记录对象是可序列化或可外部化的,则记录 组件被写入,就好像调用`defaultWriteObject` 方法一样。 b. 如果对象既不是可序列化也不是可外部化,则 抛出`NotSerializableException`。
然后
writeObject
方法返回。 -
对于常规对象,通过递归调用
writeObject
写入对象的类的ObjectStreamClass
。它只会在第一次引用时出现在流中。为对象分配一个句柄。对象的内容写入流中。
a. 如果对象是可序列化的,则找到最高可序列化类。对于该类和每个派生类,将写入该类的字段。 如果类没有`writeObject`方法,则调用 `defaultWriteObject`方法将可序列化字段写入流中。 如果类有`writeObject`方法,则调用该方法。 它可以调用`defaultWriteObject`或`putFields`和 `writeFields`来保存对象的状态,然后可以写入 其他信息到流中。 b. 如果对象是可外部化的,则调用对象的 `writeExternal`方法。 c. 如果对象既不是可序列化也不是可外部化,则 抛出`NotSerializableException`。
在遍历过程中可能会发生异常,也可能会在底层流中发生异常。对于IOException
的任何子类,异常将使用异常协议写入流中,并丢弃流状态。如果在尝试将第一个异常写入流时抛出第二个IOException
,则流将处于未知状态,并且从writeObject
中抛出StreamCorruptedException
。对于其他异常,流将被中止,并处于未知且不可用的状态。
writeUnshared
方法将一个“unshared”对象写入ObjectOutputStream
。该方法与writeObject
相同,只是它总是将给定对象作为流中的新对象写入(而不是指向先前序列化实例的后向引用)。具体来说:
-
通过
writeUnshared
写入的对象总是以与新出现对象相同的方式序列化(即尚未写入流的对象),无论该对象是否先前已被写入。 -
如果使用
writeObject
写入先前使用writeUnshared
写入的对象,则将处理先前的writeUnshared
操作,就好像它是对一个单独对象的写入一样。换句话说,ObjectOutputStream
永远不会生成指向通过调用writeUnshared
写入的对象数据的后向引用。
通过writeUnshared
写入对象本身并不能保证在反序列化时获得唯一引用,但它允许在流中多次定义单个对象,以便接收方通过多次调用ObjectInputStream.readUnshared
方法(参见第3.1节,“ObjectInputStream类”)不会发生冲突。请注意,上述规则仅适用于使用writeUnshared
写入的基本级别对象,而不适用于要序列化的对象图中的任何传递引用的子对象。
defaultWriteObject
方法实现了当前类的默认序列化机制。此方法只能从类的writeObject
方法中调用。该方法将当前类的所有可序列化字段写入流中。如果从writeObject
方法之外调用,将抛出NotActiveException
。
putFields
方法返回一个PutField
对象,调用者用于设置流中可序列化字段的值。可以以任何顺序设置字段。设置完所有字段后,必须调用writeFields
以按规范顺序将字段值写入流中。如果未设置字段,则将适当类型的默认值写入流中。此方法只能从可序列化类的writeObject
方法中调用。它不能被多次调用,也不能在调用defaultWriteObject
后调用。只有在调用writeFields
后才能向流中写入其他数据。
reset
方法将流状态重置为刚构造时的状态。Reset
将丢弃已写入流中的任何对象的状态。流中的当前点被标记为重置,因此相应的ObjectInputStream
将在相同点重置。先前写入流中的对象不会被记为已经写入流中。它们将再次写入流中。当必须再次发送对象的内容时,这很有用。在序列化对象时不能调用Reset
。如果不当调用,将抛出IOException
。
从Java 2 SDK标准版v1.3开始,当需要序列化ObjectStreamClass
时,将调用writeClassDescriptor
方法。writeClassDescriptor
负责将ObjectStreamClass
的表示写入序列化流。子类可以重写此方法以自定义类描述符写入序列化流的方式。如果重写了此方法,则应该还重写ObjectInputStream
中的相应readClassDescriptor
方法,以从其自定义流表示中重建类描述符。默认情况下,writeClassDescriptor
根据第6.4节,“流格式语法”中指定的格式写入类描述符。请注意,只有在ObjectOutputStream
不使用旧的序列化流格式时才会调用此方法(参见第6.3节,“流协议版本”)。如果序列化流使用旧格式(ObjectStreamConstants.PROTOCOL_VERSION_1
),则类描述符将以无法被覆盖或自定义的方式内部写入。
在序列化Class
时,将调用annotateClass
方法,并在类描述符写入流后调用。子类可以扩展此方法并向流中写入有关类的其他信息。此信息必须由相应的ObjectInputStream
子类中的resolveClass
方法读取。
ObjectOutputStream
子类可以实现replaceObject
方法来在序列化过程中监视或替换对象。替换对象必须通过在调用第一个要替换的对象的writeObject
之前显式调用enableReplaceObject
来启用。一旦启用,将在首次序列化对象之前为每个对象调用replaceObject
。请注意,replaceObject
方法不会用于特殊处理的类Class
和ObjectStreamClass
的对象。子类的实现可以返回一个可序列化的替代对象,该对象将代替原始对象进行序列化。流中对原始对象的所有引用都将被替换为替代对象。
在替换对象时,子类必须确保替换的对象与将存储引用的每个字段兼容,或者在反序列化过程中将进行补充替换。类型不是字段或数组元素类型的子类的对象将在稍后通过引发ClassCastException
中止反序列化,并且引用将不会被存储。
enableReplaceObject
方法可以由ObjectOutputStream
的受信任子类调用,以在序列化过程中启用一个对象替换另一个对象。替换对象在调用enableReplaceObject
并传入true
值之前是禁用的。之后可以通过将其设置为false
来禁用。将返回先前的设置。enableReplaceObject
方法检查请求替换的流是否可信。为确保不会意外暴露对象的私有状态,只有受信任的流子类才能使用replaceObject
。受信任的类是属于具有启用可序列化替换权限的安全保护域的类。
如果ObjectOutputStream
的子类不被视为系统域的一部分,则必须将SerializablePermission "enableSubstitution"
添加到安全策略文件中。如果ObjectInputStream
的子类的保护域没有通过调用enableReplaceObject
获得"enableSubstitution"
权限,则将抛出AccessControlException
。有关安全模型的更多信息,请参阅文档Java安全体系结构(JDK1.2)。
writeStreamHeader
方法将魔数和版本写入流中。这些信息必须由ObjectInputStream
的readStreamHeader
方法读取。子类可能需要实现此方法以识别流的唯一格式。
flush
方法用于清空流中保存的任何缓冲区,并将刷新转发给底层流。子类可以使用drain
方法仅清空ObjectOutputStream
的缓冲区,而无需强制刷新底层流。
所有基本类型的写入方法都使用DataOutputStream
对其值进行编码,以将其放入标准流格式中。字节被缓冲到块数据记录中,以便可以将其与对象的编码区分开。此缓冲允许必要时跳过原始数据以进行类版本控制。它还允许解析流而不调用特定于类的方法。
要覆盖序列化的实现,ObjectOutputStream
的子类应调用受保护的无参数ObjectOutputStream
构造函数。在无参数构造函数中有一个SerializablePermission "enableSubclassImplementation"
的安全检查,以确保只有受信任的类允许覆盖默认实现。此构造函数不为ObjectOutputStream
分配任何私有数据,并设置一个标志,指示最终的writeObject
方法应调用writeObjectOverride
方法并返回。所有其他ObjectOutputStream
方法都不是final的,可以直接被子类覆盖。
2.2 ObjectOutputStream.PutField类
类PutField
提供了为一个类的可序列化字段设置值的API,当该类不使用默认序列化时。每个方法将指定的命名值放入流中。如果name
与正在写入字段的类的可序列化字段的名称不匹配,或者命名字段的类型与调用的特定put
方法的第二个参数类型不匹配,则会抛出IllegalArgumentException
。
2.3 writeObject方法
对于可序列化对象,writeObject
方法允许一个类控制其自身字段的序列化。以下是其签名:
private void writeObject(ObjectOutputStream stream)
throws IOException;
可序列化对象的每个子类都可以定义自己的writeObject
方法。如果一个类没有实现该方法,则将使用defaultWriteObject
提供的默认序列化。当实现时,该类只负责写入自己的字段,而不是其超类型或子类型的字段。
如果实现了类的writeObject
方法,则负责保存类的状态。在写入任何将由相应的readObject
方法需要的可选数据之前,必须调用一次(仅一次)ObjectOutputStream
的defaultWriteObject
或writeFields
方法;即使没有写入任何可选数据,也必须调用一次defaultWriteObject
或writeFields
。如果在写入可选数据(如果有)之前未调用defaultWriteObject
或writeFields
,则在ObjectInputStream
无法解析定义了相关writeObject
方法的类的情况下,实例反序列化的行为是未定义的。
可选数据的格式、结构和版本控制完全由类负责。
2.4 writeExternal方法
实现java.io.Externalizable
接口的对象必须实现writeExternal
方法来保存对象的整个状态。它必须与其超类协调以保存它们的状态。所有ObjectOutput
的方法都可用于保存对象的基本类型字段和对象字段。
public void writeExternal(ObjectOutput stream)
throws IOException;
JDK 1.2引入了一种新的写入Externalizable数据的默认格式。新格式指定原始数据将由writeExternal
方法以块数据模式写入。此外,在writeExternal
方法返回后,流中会附加一个标记,表示External对象的结束。这种格式变更的好处在第3.6节,“readExternal方法”中讨论。此更改引起的兼容性问题在第2.6节,“useProtocolVersion方法”中讨论。
2.5 writeReplace方法
对于Serializable和Externalizable类,writeReplace
方法允许对象的类在对象被写入流之前指定其自己的替代项。通过实现writeReplace
方法,一个类可以直接控制其自身实例的类型和实例被序列化时的情况。
该方法定义如下:
ANY-ACCESS-MODIFIER Object writeReplace()
throws ObjectStreamException;
当ObjectOutputStream
准备将对象写入流时,将调用writeReplace
方法。ObjectOutputStream
检查类是否定义了writeReplace
方法。如果定义了该方法,则调用writeReplace
方法,允许对象在流中指定其替代项。返回的对象应该与传入的对象类型相同,或者当读取和解析时将导致与对象所有引用兼容的类型的对象。如果不是,则在发现类型不匹配时将发生ClassCastException
。
2.6 useProtocolVersion方法
由于流协议的更改不向后兼容,已添加了一种机制,使当前虚拟机能够编写可被先前版本读取的序列化流。当使用向后兼容协议时,当然,使用新流格式纠正的问题将存在。
流协议版本在第6.3节,“流协议版本”中讨论。