目次 | 前の項目 | 次の項目 | Java オブジェクト直列化仕様 Version 6.0 |
第 6 章 |
ストリーム形式は、次の設計目標を実現しました。
基本構造では、ストリーム内でオブジェクトを表す必要があります。オブジェクトのクラス、フィールド、およびクラス固有のメソッドによって書き込まれ、あとで読み取られるデータなどの属性を表現する必要があります。ストリームのオブジェクトの表現は、ある文法によって記述することができます。null オブジェクト、新規オブジェクト、クラス、配列、文字列、およびすでにストリームにあるオブジェクトへのバック参照に対し、特別な表現があります。ストリームに書き込まれた各オブジェクトには、このオブジェクトをバック参照するために使用するハンドルが割り当てられます。ハンドルは、0x7E0000 から始めて順次割り当てられます。ストリームがリセットされると、ハンドルは再び 0x7E0000 から始まります。
ダイナミックプロキシクラス以外のクラスのObjectStreamClass
オブジェクトは、次の要素によって表されます。
- クラスのさまざまなプロパティーを表すフラグのセット。 クラスが
writeObject
メソッドを定義しているか、クラスが直列化可能、外部化可能、または enum 型か、など- 直列化可能フィールドの数
- デフォルトの機構によって直列化されたクラスのフィールドの配列。配列やオブジェクトでは、フィールドの型が文字列として格納される。この文字列は、「JavaTM Virtual Machine Specification, Second Edition」の 4.3.2 に指定されているフィールド記述子の形式 ("
Ljava/lang/Object;
"など) でなければならないannotateClass
メソッドによって書き込まれる任意指定のブロックデータレコードまたはオブジェクトObjectStreamClass
のスーパータイプ (スーパークラスが直列化可能でなければ null)
ダイナミックプロキシクラスのObjectStreamClass
オブジェクトは、次の要素によって表されます。
String
オブジェクトの表現は長さの情報で構成され、その情報のあとに変更後の UTF-8 で符号化された文字列の内容が続きます。 変更後の UTF-8 エンコーディングは、JavaTM 仮想マシンと、java.io.DataInput
およびDataOutput
インタフェースで使用されるものと同じです。ただし、補助文字と null 文字の表現で、標準 UTB-8 とは異なります。長さの情報の形式は、変更後の UTF-8 エンコーディングでの文字列の長さにより異なります。 指定されたString
の変更後の UTF-8 エンコーディング長が 65,536 バイト未満の場合、長さは、符号なし 16 ビット整数を表す 2 バイトとして書き込まれます。JavaTM 2 platform, Standard Edition, v1.3 以降は、変更後の UTF-8 エンコーディングによる文字列の長さが 65,536 バイト以上の場合、長さは、符号付き 64 ビット整数を表す 8 バイトで書き込まれます。直列化ストリームのString
の前にあるタイプコードは、String
の書き込みに使用された形式を表しています。
enum 定数は次の要素によって表されます。
ストリームの新規オブジェクトは次の要素によって表されます。
- 直列化可能フィールド。「1.5 クラスの直列化可能なフィールドの指定」を参照
- そのクラスに
writeObject
/readObject
メソッドがあれば、writeObject
メソッドによって書き込まれた、プリミティブ型の任意指定のオブジェクトおよび (または) ブロックデータレコードと、それに続いてendBlockData
がある場合がある
クラスによって書き込まれたすべてのプリミティブデータは、ブロックデータレコードにバッファリングされ、ラップされます。これは、そのデータがwriteObject
メソッドの中でストリームに書き込まれたのか、writeObject
メソッドの外から直接ストリームに書き込まれたのかには関係ありません。このデータは、対応するreadObject
メソッドによって読み込むか、ストリームから直接読み込むことができます。writeObject
メソッドによって書き込まれたオブジェクトは、前に書き込まれたブロックデータレコードがあればそれを停止し、正規オブジェクト、null、またはバック参照のどれかのうちで、適切なものとして書き込まれます。ブロックデータレコードでは、エラーの回復によって任意指定データを破棄することができます。クラスの中から呼び出された場合には、ストリームはデータやオブジェクトをendBlockData
まで破棄することができます。
JDKTM 1.2 では、直列化ストリーム形式を、JDKTM 1.1 のすべてのマイナーリリースと下位互換性のない形式に変更する必要があります。下位互換性が必要な場合に備え、直列化ストリームの書き込み時に、どの PROTOCOL_VERSION を使用するかを示す機能が追加されています。メソッドObjectOutputStream.useProtocolVersion
は、直列化ストリームの書き込みに使うプロトコルのバージョンをパラメータにとります。
初期のストリーム形式を示します。 |
新しい外部データ形式を示します。プリミティブデータはブロックデータモードで書き込まれ、TC_ENDBLOCKDATA で終了します。 |
ブロックデータ境界は標準化されました。ブロックデータモードで書き込まれたプリミティブデータは、1024 バイトのデータのブロックを超えないように標準のデータ形式に戻されます。この変更の利点は、ストリーム内の直列化データ形式の仕様を強化することです。この変更は、完全に下位および上位互換性があります。 |
JDKTM 1.2 は、デフォルトではPROTOCOL_VERSION_2
を書き込みます。JDKTM 1.1 は、デフォルトでは
PROTOCOL_VERSION_1
を書き込みます。JDKTM 1.1.7 以降では、両方のバージョンを読み取ることができます。
JDKTM 1.1.7 より前のリリースでは、
PROTOCOL_VERSION_1
しか読み取ることができません。
以下の表は、ストリーム形式の文法を示したものです。非終端記号はイタリックで、終端記号は固定幅のフォントで示します。非終端の定義には、その後に「:」が続きます。定義には 1 つまたは複数の代替定義が続き、それぞれが別の行に示されます。次の表に、その表記法を示します。
表記法
意味
(datatype)
このトークンには、バイトなどのデータ型が指定される
token[n]
このトークンの事前に定義されたオカレンス数。 これは配列である
x0001
16 進数で表したリテラル値。16 進数の桁数がその値のサイズを表す
<xxx>
ストリームから読み込まれた値であり、配列の長さを示すために使用される
記号 (utf) は 2 バイトの長さ情報を使用して記述された文字列を指定する場合に使います。 (long-utf) は 8 バイトの長さ情報を使用して記述された文字列を指定する場合に使います。詳細については、「6.2 ストリーム要素」を参照してください。
直列化されたストリームは、ストリーム規則を満たす任意のストリームによって表されます。stream: magic version contentscontents: content contents contentcontent: object blockdataobject: newObject newClass newArray newString newEnum newClassDesc prevObject nullReference exception TC_RESETnewClass: TC_CLASS classDesc newHandleclassDesc: newClassDesc nullReference (ClassDesc)prevObject // an object required to be of type // ClassDescsuperClassDesc: classDescnewClassDesc: TC_CLASSDESC className serialVersionUID newHandle classDescInfo TC_PROXYCLASSDESC newHandle proxyClassDescInfoclassDescInfo: classDescFlags fields classAnnotation superClassDescclassName: (utf)serialVersionUID: (long)classDescFlags: (byte) // Defined in Terminal Symbols and // ConstantsproxyClassDescInfo: (int)<count> proxyInterfaceName[count] classAnnotation superClassDescproxyInterfaceName:(utf)fields: (short)<count> fieldDesc[count]fieldDesc: primitiveDesc objectDescprimitiveDesc: prim_typecode fieldNameobjectDesc: obj_typecode fieldName className1fieldName: (utf)className1: (String)object // String containing the field's type, // in field descriptor formatclassAnnotation: endBlockData contents endBlockData // contents written by annotateClassprim_typecode: `B' // byte `C' // char `D' // double `F' // float `I' // integer `J' // long `S' // short `Z' // booleanobj_typecode: `[` // array `L' // objectnewArray: TC_ARRAY classDesc newHandle (int)<size> values[size]newObject: TC_OBJECT classDesc newHandle classdata[] // data for each classclassdata: nowrclass // SC_SERIALIZABLE & classDescFlag && // !(SC_WRITE_METHOD & classDescFlags) wrclass objectAnnotation // SC_SERIALIZABLE & classDescFlag && // SC_WRITE_METHOD & classDescFlags externalContents // SC_EXTERNALIZABLE & classDescFlag && // !(SC_BLOCKDATA & classDescFlags objectAnnotation // SC_EXTERNALIZABLE & classDescFlag&& // SC_BLOCKDATA & classDescFlagsnowrclass: values // fields in order of class descriptorwrclass: nowrclassobjectAnnotation: endBlockData contents endBlockData // contents written by writeObject // or writeExternal PROTOCOL_VERSION_2.blockdata: blockdatashort blockdatalongblockdatashort: TC_BLOCKDATA (unsigned byte)<size> (byte)[size]blockdatalong: TC_BLOCKDATALONG (int)<size> (byte)[size]endBlockData : TC_ENDBLOCKDATAexternalContent: // Only parseable by readExternal ( bytes) // primitive data objectexternalContents: // externalContent written by externalContent // writeExternal in PROTOCOL_VERSION_1. externalContents externalContentnewString: TC_STRING newHandle (utf) TC_LONGSTRING newHandle (long-utf)newEnum: TC_ENUM classDesc newHandle enumConstantNameenumConstantName: (String)objectprevObject TC_REFERENCE (int)handlenullReference TC_NULLexception: TC_EXCEPTION reset (Throwable)object resetmagic: STREAM_MAGICversion STREAM_VERSIONvalues: // The size and types are described by the // classDesc for the current objectnewHandle: // The next number in sequence is assigned // to the object being serialized or deserializedreset: // The set of known objects is discarded // so the objects of the exception do not // overlap with the previously sent objects // or with objects that may be sent after // the exception
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; //if SC_SERIALIZABLE final static byte SC_BLOCK_DATA = 0x08; //if SC_EXTERNALIZABLE final static byte SC_SERIALIZABLE = 0x02; final static byte SC_EXTERNALIZABLE = 0x04; final static byte SC_ENUM = 0x10;ストリームを書き込んでいる Serializable クラスにwriteObject
メソッドがあり、それがストリームに追加データを書き込んだ可能性があると、フラグ SC_WRITE_METHOD はオンに設定されます。この場合には、そのクラスのデータは常に TC_ENDBLOCKDATA マーカーで終わっていなければなりません。
STREAM_PROTOCOL_2
を使ってExternalizable
クラスがストリームに書き込まれる場合は、フラグ SC_BLOCKDATA が設定されます。JDKTM 1.2 のデフォルトでは、このプロトコルを使ってExternalizable
オブジェクトがストリームに書き込まれます。JDKTM 1.1 では、STREAM_PROTOCOL_1 を使って書き込まれます。ストリームを書き込んだクラスが
java.io.Serializable
を拡張したが、java.io.Externalizable
は拡張しなかった場合は、フラグ SC_SERIALIZABLE がオンに設定されます。 そのストリームを読み込むクラスもまたjava.io.Serializable
を拡張して、デフォルトの直列化機構を使用しなければなりません。ストリームを書き込んだクラスが
java.io.Externalizable
を拡張した場合は、フラグ SC_EXTERNALIZABLE がオンに設定されます。 そのデータを読み込むクラスもまた、Externalizable
を拡張して、デフォルトの直列化機構を使用しなければなりません。 このデータはそのwriteExternal
とreadExternal
メソッドを使って読み込まれます。ストリームに書き込まれたクラスが enum 型であれば、フラグ SC_ENUM が設定されます。受け取り側の対応するクラスも enum 型でなければなりません。enum 型の定数のデータは、「1.12 Enum 定数の直列化」で説明するように、読み書きされます。
オリジナルクラスと、リンクリストの 2 つのインスタンスの場合を想定します。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.~..<