Javaシリアル化アルゴリズムが明らかに

シリアル化は、オブジェクトの状態を一連のバイトに保存するプロセスです。デシリアライズは、これらのバイトをライブオブジェクトに再構築するプロセスです。Java Serialization APIは、開発者がオブジェクトのシリアル化を処理するための標準メカニズムを提供します。このヒントでは、オブジェクトをシリアル化する方法と、シリアル化が必要になる場合がある理由を説明します。Javaで使用されるシリアル化アルゴリズムについて学習し、オブジェクトのシリアル化された形式を示す例を参照してください。完了するまでに、シリアル化アルゴリズムがどのように機能するか、およびどのエンティティがオブジェクトの一部として低レベルでシリアル化されるかについての確かな知識が必要です。

シリアル化が必要なのはなぜですか?

今日の世界では、一般的なエンタープライズアプリケーションには複数のコンポーネントがあり、さまざまなシステムやネットワークに分散されます。 Javaでは、すべてがオブジェクトとして表されます。 2つのJavaコンポーネントが相互に通信する場合は、データを交換するメカニズムが必要です。これを実現する1つの方法は、独自のプロトコルを定義してオブジェクトを転送することです。つまり、受信側は、送信者がオブジェクトを再作成するために使用するプロトコルを知っている必要があり、サードパーティのコンポーネントとの通信が非常に困難になります。したがって、コンポーネント間でオブジェクトを転送するには、汎用的で効率的なプロトコルが必要です。シリアル化はこの目的のために定義されており、Javaコンポーネントはこのプロトコルを使用してオブジェクトを転送します。

図1は、クライアント/サーバー通信の概要を示しています。ここでは、オブジェクトがシリアル化によってクライアントからサーバーに転送されます。

図1.実行中のシリアル化の概要(クリックして拡大)

オブジェクトをシリアル化する方法

オブジェクトをシリアル化するにjava.io.Serializableは、リスト1に示すように、オブジェクトのクラスがインターフェースを実装していることを確認する必要があります。

リスト1.Serializableの実装

 import java.io.Serializable; class TestSerial implements Serializable { public byte version = 100; public byte count = 0; } 

リスト1では、通常のクラスの作成とは異なる方法で行う必要があるのは、java.io.Serializableインターフェースを実装することだけです。Serializableインターフェイスは、マーカーインターフェイスです。メソッドをまったく宣言していません。これは、クラスをシリアル化できることをシリアル化メカニズムに通知します。

クラスをシリアル化の対象にしたので、次のステップは実際にオブジェクトをシリアル化することです。これは、リスト2に示すようにwriteObject()java.io.ObjectOutputStreamクラスのメソッドを呼び出すことによって行われます。

リスト2.writeObject()の呼び出し

 public static void main(String args[]) throws IOException { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); TestSerial ts = new TestSerial(); oos.writeObject(ts); oos.flush(); oos.close(); } 

リスト2は、TestSerialオブジェクトの状態をtemp.out。というファイルに保管します。oos.writeObject(ts);実際にシリアル化アルゴリズムを開始し、シリアル化アルゴリズムがオブジェクトをに書き込みますtemp.out

永続ファイルからオブジェクトを再作成するには、リスト3のコードを使用します。

リスト3.シリアル化されたオブジェクトの再作成

 public static void main(String args[]) throws IOException { FileInputStream fis = new FileInputStream("temp.out"); ObjectInputStream oin = new ObjectInputStream(fis); TestSerial ts = (TestSerial) oin.readObject(); System.out.println("version="+ts.version); } 

リスト3では、オブジェクトの復元はoin.readObject()メソッド呼び出しで行われます。このメソッド呼び出しは、以前に永続化したrawバイトを読み込み、元のオブジェクトグラフの正確なレプリカであるライブオブジェクトを作成します。readObject()シリアル化可能なオブジェクトを読み取ることができるため、正しいタイプへのキャストが必要です。

このコードを実行するとversion=100、標準出力に出力されます。

オブジェクトのシリアル化された形式

オブジェクトのシリアル化されたバージョンはどのように見えますか?前のセクションのサンプルコードは、TestSerialオブジェクトのシリアル化されたバージョンをファイルに保存したことを忘れないでくださいtemp.out。リスト4は、の内容をtemp.out16進数で示しています。(16進形式で出力を表示するには、16進エディターが必要です。)

リスト4.TestSerialの16進形式

 AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65 73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05 63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78 70 00 64 

TestSerialリスト5に示すように、実際のオブジェクトをもう一度見ると、2バイトのメンバーしかないことがわかります。

リスト5.TestSerialのバイトメンバー

 public byte version = 100; public byte count = 0; 

バイト変数のサイズは1バイトであるため、オブジェクト(ヘッダーなし)の合計サイズは2バイトです。ただし、リスト4のシリアル化されたオブジェクトのサイズを見ると、51バイトであることがわかります。驚き!余分なバイトはどこから来たのですか、そしてそれらの重要性は何ですか?これらはシリアル化アルゴリズムによって導入され、オブジェクトを再作成するために必要です。次のセクションでは、このアルゴリズムについて詳しく説明します。

Javaのシリアル化アルゴリズム

これで、オブジェクトをシリアル化する方法についてかなりの知識が得られたはずです。しかし、プロセスは内部でどのように機能しますか?一般に、シリアル化アルゴリズムは次のことを行います。

  • インスタンスに関連付けられたクラスのメタデータを書き出します。
  • が見つかるまで、スーパークラスの説明を再帰的に書き出しますjava.lang.object
  • メタデータ情報の書き込みが完了すると、インスタンスに関連付けられている実際のデータから開始されます。しかし今回は、最上位のスーパークラスから始まります。
  • インスタンスに関連付けられたデータを、最小のスーパークラスから最大の派生クラスまで再帰的に書き込みます。

このセクションでは、考えられるすべてのケースをカバーする別のサンプルオブジェクトを作成しました。シリアル化される新しいサンプルオブジェクトをリスト6に示します。

リスト6.シリアル化されたオブジェクトのサンプル

 class parent implements Serializable { int parentVersion = 10; } class contain implements Serializable{ int containVersion = 11; } public class SerialTest extends parent implements Serializable { int version = 66; contain con = new contain(); public int getVersion() { return version; } public static void main(String args[]) throws IOException { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); SerialTest st = new SerialTest(); oos.writeObject(st); oos.flush(); oos.close(); } } 

This example is a straightforward one. It serializes an object of type SerialTest, which is derived from parent and has a container object, contain. The serialized format of this object is shown in Listing 7.

Listing 7. Serialized form of sample object

 AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65 73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07 76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09 4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72 65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00 0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70 00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74 61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00 0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78 70 00 00 00 0B 

Figure 2 offers a high-level look at the serialization algorithm for this scenario.

Figure 2. An outline of the serialization algorithm

Let's go through the serialized format of the object in detail and see what each byte represents. Begin with the serialization protocol information:

  • AC ED: STREAM_MAGIC. Specifies that this is a serialization protocol.
  • 00 05: STREAM_VERSION. The serialization version.
  • 0x73: TC_OBJECT. Specifies that this is a new Object.

The first step of the serialization algorithm is to write the description of the class associated with an instance. The example serializes an object of type SerialTest, so the algorithm starts by writing the description of the SerialTest class.

  • 0x72: TC_CLASSDESC. Specifies that this is a new class.
  • 00 0A: Length of the class name.
  • 53 65 72 69 61 6c 54 65 73 74: SerialTest, the name of the class.
  • 05 52 81 5A AC 66 02 F6: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This particular flag says that the object supports serialization.
  • 00 02: Number of fields in this class.

Next, the algorithm writes the field int version = 66;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 07: Length of the field name.
  • 76 65 72 73 69 6F 6E: version, the name of the field.

And then the algorithm writes the next field, contain con = new contain();. This is an object, so it will write the canonical JVM signature of this field.

  • 0x74: TC_STRING. Represents a new string.
  • 00 09: Length of the string.
  • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain;, the canonical JVM signature.
  • 0x78: TC_ENDBLOCKDATA, the end of the optional block data for an object.

The next step of the algorithm is to write the description of the parent class, which is the immediate superclass of SerialTest.

  • 0x72: TC_CLASSDESC. Specifies that this is a new class.
  • 00 06: Length of the class name.
  • 70 61 72 65 6E 74: SerialTest, the name of the class
  • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This flag notes that the object supports serialization.
  • 00 01: Number of fields in this class.

Now the algorithm will write the field description for the parent class. parent has one field, int parentVersion = 100;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 0D: Length of the field name.
  • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion, the name of the field.
  • 0x78: TC_ENDBLOCKDATA, the end of block data for this object.
  • 0x70: TC_NULL, which represents the fact that there are no more superclasses because we have reached the top of the class hierarchy.

So far, the serialization algorithm has written the description of the class associated with the instance and all its superclasses. Next, it will write the actual data associated with the instance. It writes the parent class members first:

  • 00 00 00 0A: 10, the value of parentVersion.

Then it moves on to SerialTest.

  • 00 00 00 42: 66, the value of version.

The next few bytes are interesting. The algorithm needs to write the information about the contain object, shown in Listing 8.

Listing 8. The contain object

 contain con = new contain(); 

Remember, the serialization algorithm hasn't written the class description for the contain class yet. This is the opportunity to write this description.

  • 0x73: TC_OBJECT, designating a new object.
  • 0x72: TC_CLASSDESC.
  • 00 07: Length of the class name.
  • 63 6F 6E 74 61 69 6E: contain, the name of the class.
  • FC BB E6 0E FB CB 60 C7: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This flag indicates that this class supports serialization.
  • 00 01: Number of fields in this class.

Next, the algorithm must write the description for contain's only field, int containVersion = 11;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 0E: Length of the field name.
  • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, the name of the field.
  • 0x78: TC_ENDBLOCKDATA.

Next, the serialization algorithm checks to see if contain has any parent classes. If it did, the algorithm would start writing that class; but in this case there is no superclass for contain, so the algorithm writes TC_NULL.

  • 0x70: TC_NULL.

Finally, the algorithm writes the actual data associated with contain.

  • 00 00 00 0B: 11, the value of containVersion.

Conclusion

In this tip, you have seen how to serialize an object, and learned how the serialization algorithm works in detail. I hope this article gives you more detail on what happens when you actually serialize an object.

About the author

Sathiskumar Palaniappanは、IT業界で4年以上の経験があり、Java関連のテクノロジーを3年以上使用しています。現在、彼はIBMLabsのJavaTechnologyCenterでシステムソフトウェアエンジニアとして働いています。彼はまた、電気通信業界での経験もあります。

リソース

  • Javaオブジェクトのシリアル化仕様を読んでください。(仕様はPDFです。)
  • 「オブジェクトをフラット化する:Javaシリアル化APIの秘密を発見する」(ToddM。Greanier、JavaWorld、2000年7月)では、シリアル化プロセスの要点について説明しています。
  • Java RMIの第10章(William Grosso、O'Reilly、2001年10月)も参考になります。

このストーリー「明らかにされたJavaシリアル化アルゴリズム」は、もともとJavaWorldによって公開されました。