Java Object Serialization Specification: 6 - Object Serialization Stream Protocol


6.1 概述

流格式满足以下设计目标:

6.2 流元素

需要基本结构来表示流中的对象。需要表示对象的每个属性:其类、其字段以及由类特定方法编写和后来读取的数据。可以用语法描述流中对象的表示。对于空对象、新对象、类、数组、字符串以及对流中已有对象的后向引用,都有特殊表示。写入流的每个对象都被分配一个句柄,用于引用该对象。句柄从0x7E0000开始按顺序分配。当流被重置时,句柄重新从0x7E0000开始。

类对象由以下表示:

对于不是动态代理类的类的ObjectStreamClass对象由以下表示:

对于动态代理类的ObjectStreamClass对象由以下表示:

String对象的表示包括长度信息,后跟使用修改后的UTF-8编码的字符串内容。修改后的UTF-8编码与Java虚拟机中使用的编码以及java.io.DataInputDataOutput接口中使用的编码相同;它与标准UTF-8在表示补充字符和空字符方面有所不同。长度信息的形式取决于使用修改后的UTF-8编码的字符串的长度。如果给定String的修改后的UTF-8编码长度不超过65536字节,则长度将写为表示无符号16位整数的2字节。从Java 2平台,标准版,v1.3开始,如果使用修改后的UTF-8编码的字符串的长度为65536字节或更多,则长度将写为表示有符号64位整数的8字节。在序列化流中在String之前的类型码指示写入String时使用的格式。

数组由以下表示:

枚举常量由以下表示:

流中的新对象由以下表示:

由类编写的所有原始数据都被缓冲并包装在块数据记录中,无论数据是在writeObject方法内部写入流还是直接从writeObject方法外部写入流。这些数据只能由相应的readObject方法读取,或者直接从流中读取。由writeObject方法编写的对象终止任何先前的块数据记录,并根据需要将其写入为常规对象、null或后向引用。块数据记录允许错误恢复以丢弃任何可选数据。当从类内部调用时,流可以丢弃任何数据或对象,直到endBlockData

6.3 流协议版本

在JDK 1.2中对序列化流格式进行了一项不向后兼容的更改,不适用于JDK 1.1的所有次要版本。为了提供需要向后兼容的情况,添加了一项功能,用于指示在写入序列化流时要使用哪个PROTOCOL_VERSION。方法ObjectOutputStream.useProtocolVersion以参数形式接受要用于写入序列化流的协议版本。

流协议版本如下:

JDK 1.2默认写入PROTOCOL_VERSION_2

JDK 1.1默认写入PROTOCOL_VERSION_1

JDK 1.1.7及更高版本可以读取两个版本。

JDK 1.1.7之前的版本只能读取PROTOCOL_VERSION_1

6.4 流格式的语法

下表包含流格式的语法。非终结符以斜体显示。终结符以固定宽度字体显示。非终结符的定义后跟一个“:”。定义后跟一个或多个备选项,每个备选项占据单独的一行。以下表描述了符号:

符号 含义
(数据类型) 此标记具有指定的数据类型,例如byte。
标记[n] 标记的预定义数量出现,即数组。
x0001 以十六进制表示的文字值。十六进制数字的数量反映了值的大小。
<xxx> 从流中读取的值,用于指示数组的长度。

请注意,符号(utf)用于指定使用2字节长度信息编写的字符串,而符号(long-utf)用于指定使用8字节长度信息编写的字符串。有关详细信息,请参阅第6.2节,“流元素”

6.4.1 语法规则

序列化流由满足stream规则的任何流表示。

流:
  魔法 版本 内容

内容:
  内容
  内容 内容

内容:
  对象
  块数据

对象:
  新对象
  新类
  新数组
  新字符串
  新枚举
  新类描述
  前一个对象
  空引用
  异常
  TC_RESET

新类:
  TC_CLASS 类描述 新句柄

类描述:
  新类描述
  空引用
  (ClassDesc)前一个对象      // 要求为ClassDesc类型的对象

超类描述:
  类描述

新类描述:
  TC_CLASSDESC 类名 序列化版本号 新句柄 类描述信息
  TC_PROXYCLASSDESC 新句柄 代理类描述信息

类描述信息:
  类描述标志 字段 类注解 超类描述

类名:
  (utf)

序列化版本号:
  (long)

类描述标志:
  (byte)                  // 在终端符号和常量中定义

代理类描述信息:
  (int)<数量> 代理接口名[数量] 类注解
      超类描述

代理接口名:
  (utf)

字段:
  (short)<数量> 字段描述[数量]

字段描述:
  基本描述
  对象描述

基本描述:
  基本类型码 字段名

对象描述:
  对象类型码 字段名 类名1

字段名:
  (utf)

类名1:
  (String)对象             // 包含字段类型的字符串,
                             // 以字段描述符格式

类注解:
  结束块数据
  内容 结束块数据      // 由annotateClass编写的内容

基本类型码:
  'B'       // 字节
  'C'       // 字符
  'D'       // 双精度
  'F'       // 浮点
  'I'       // 整数
  'J'       // 长整型
  'S'       // 短整型
  'Z'       // 布尔型

对象类型码:
  '['       // 数组
  'L'       // 对象

新数组:
  TC_ARRAY 类描述 新句柄 (int)<大小> 值[大小]

新对象:
  TC_OBJECT 类描述 新句柄 类数据[]  // 每个类的数据

类数据:
  无写类                 // SC_SERIALIZABLE & 类描述标志 &&
                            // !(SC_WRITE_METHOD & 类描述标志)
  有写类 对象注解  // SC_SERIALIZABLE & 类描述标志 &&
                            // SC_WRITE_METHOD & 类描述标志
  外部内容          // SC_EXTERNALIZABLE & 类描述标志 &&
                            // !(SC_BLOCKDATA  & 类描述标志
  对象注解          // SC_EXTERNALIZABLE & 类描述标志&&
                            // SC_BLOCKDATA & 类描述标志

无写类:
  值                    // 按类描述符顺序的字段

有写类:
  无写类

对象注解:
  结束块数据
  内容 结束块数据     // 由writeObject或writeExternal PROTOCOL_VERSION_2编写的内容

块数据:
  短块数据
  长块数据

短块数据:
  TC_BLOCKDATA (无符号字节)<大小> (字节)[大小]

长块数据:
  TC_BLOCKDATALONG (int)<大小> (字节)[大小]

结束块数据:
  TC_ENDBLOCKDATA

外部内容:         // 只能由readExternal解析
  (字节)                // 基本数据
   对象

外部内容:
  writeExternal中写入的外部内容  // PROTOCOL_VERSION_1中的外部内容
  外部内容 writeExternal

新字符串:
  TC_STRING 新句柄 (utf)
  TC_LONGSTRING 新句柄 (long-utf)

新枚举:
  TC_ENUM 类描述 新句柄 枚举常量名

枚举常量名:
  (String)对象

前一个对象:
  TC_REFERENCE (int)句柄

空引用:
  TC_NULL

异常:
  TC_EXCEPTION 重置 (Throwable)对象 重置

魔法:
  STREAM_MAGIC

版本:
  STREAM_VERSION

值:          // 当前对象的类描述描述了大小和类型

新句柄:       // 分配给正在序列化或反序列化的对象的下一个序号

重置:           // 已知对象集被丢弃,因此异常的对象不会与先前发送的对象或在异常之后可能发送的对象重叠

6.4.2 终端符号和常量

java.io.ObjectStreamConstants中的以下符号定义了流中预期的终端和常量值。

final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
final static byte TC_NULL = (byte)0x70;
final static byte TC_REFERENCE = (byte)0x71;
final static byte TC_CLASSDESC = (byte)0x72;
final static byte TC_OBJECT = (byte)0x73;
final static byte TC_STRING = (byte)0x74;
final static byte TC_ARRAY = (byte)0x75;
final static byte TC_CLASS = (byte)0x76;
final static byte TC_BLOCKDATA = (byte)0x77;
final static byte TC_ENDBLOCKDATA = (byte)0x78;
final static byte TC_RESET = (byte)0x79;
final static byte TC_BLOCKDATALONG = (byte)0x7A;
final static byte TC_EXCEPTION = (byte)0x7B;
final static byte TC_LONGSTRING = (byte) 0x7C;
final static byte TC_PROXYCLASSDESC = (byte) 0x7D;
final static byte TC_ENUM = (byte) 0x7E;
final static  int   baseWireHandle = 0x7E0000;

标志字节 classDescFlags 可能包括以下值

final static byte SC_WRITE_METHOD = 0x01; //如果SC_SERIALIZABLE
final static byte SC_BLOCK_DATA = 0x08;    //如果SC_EXTERNALIZABLE
final static byte SC_SERIALIZABLE = 0x02;
final static byte SC_EXTERNALIZABLE = 0x04;
final static byte SC_ENUM = 0x10;

标志 SC_WRITE_METHOD 如果可序列化类写入流的类具有可能向流中写入附加数据的writeObject方法,则设置。在这种情况下,始终期望有一个TC_ENDBLOCKDATA标记来终止该类的数据。

标志 SC_BLOCKDATA 如果使用STREAM_PROTOCOL_2Externalizable类写入流中,则设置。默认情况下,这是JDK 1.2中用于将Externalizable对象写入流的协议。JDK 1.1写入STREAM_PROTOCOL_1

标志 SC_SERIALIZABLE 如果写入流的类扩展了java.io.Serializable但没有扩展java.io.Externalizable,则读取流的类也必须扩展java.io.Serializable,并且将使用默认的序列化机制。

标志 SC_EXTERNALIZABLE 如果写入流的类扩展了java.io.Externalizable,则读取数据的类也必须扩展Externalizable,并且将使用其writeExternalreadExternal方法读取数据。

标志 SC_ENUM 如果写入流的类是枚举类型,则设置。接收者的相应类也必须是枚举类型。枚举类型的常量数据将按照第1.12节,“枚举常量的序列化”中描述的方式写入和读取。

示例

考虑一个原始类和链表中的两个实例的情况:

class List implements java.io.Serializable {
    int value;
    List next;
    public static void main(String[] args) {
        try {
            List list1 = new List();
            List list2 = new List();
            list1.value = 17;
            list1.next = list2;
            list2.value = 19;
            list2.next = null;

            ByteArrayOutputStream o = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(o);
            out.writeObject(list1);
            out.writeObject(list2);
            out.flush();
            ...
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

生成的流包含:

    00: ac ed 00 05 73 72 00 04 4c 69 73 74 69 c8 8a 15 >....sr..Listi...<
    10: 40 16 ae 68 02 00 02 49 00 05 76 61 6c 75 65 4c >Z......I..valueL<
    20: 00 04 6e 65 78 74 74 00 06 4c 4c 69 73 74 3b 78 >..nextt..LList;x<
    30: 70 00 00 00 11 73 71 00 7e 00 00 00 00 00 13 70 >p....sq.~......p<
    40: 71 00 7e 00 03                                  >q.~..<