L'algorithme de sérialisation Java révélé

La sérialisation est le processus d'enregistrement de l'état d'un objet dans une séquence d'octets; la désérialisation est le processus de reconstruction de ces octets en un objet actif. L'API de sérialisation Java fournit un mécanisme standard permettant aux développeurs de gérer la sérialisation d'objets. Dans cette astuce, vous verrez comment sérialiser un objet et pourquoi la sérialisation est parfois nécessaire. Vous en apprendrez plus sur l'algorithme de sérialisation utilisé en Java et vous verrez un exemple qui illustre le format sérialisé d'un objet. Lorsque vous avez terminé, vous devriez avoir une solide connaissance du fonctionnement de l'algorithme de sérialisation et des entités sérialisées dans le cadre de l'objet à un niveau bas.

Pourquoi la sérialisation est-elle requise?

Dans le monde d'aujourd'hui, une application d'entreprise typique aura plusieurs composants et sera distribuée sur divers systèmes et réseaux. En Java, tout est représenté sous forme d'objets; si deux composants Java veulent communiquer entre eux, il doit y avoir un mécanisme d'échange de données. Une façon d'y parvenir est de définir votre propre protocole et de transférer un objet. Cela signifie que l'extrémité de réception doit connaître le protocole utilisé par l'expéditeur pour recréer l'objet, ce qui rendrait très difficile la communication avec des composants tiers. Par conséquent, il doit y avoir un protocole générique et efficace pour transférer l'objet entre les composants. La sérialisation est définie à cet effet et les composants Java utilisent ce protocole pour transférer des objets.

La figure 1 montre une vue de haut niveau de la communication client / serveur, où un objet est transféré du client au serveur par sérialisation.

Figure 1. Une vue de haut niveau de la sérialisation en action (cliquez pour agrandir)

Comment sérialiser un objet

Afin de sérialiser un objet, vous devez vous assurer que la classe de l'objet implémente l' java.io.Serializableinterface, comme indiqué dans la liste 1.

Listing 1. Implémentation de Serializable

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

Dans le listing 1, la seule chose que vous deviez faire différemment de la création d'une classe normale est d'implémenter l' java.io.Serializableinterface. L' Serializableinterface est une interface de marqueur; il ne déclare aucune méthode du tout. Il indique au mécanisme de sérialisation que la classe peut être sérialisée.

Maintenant que vous avez rendu la classe éligible pour la sérialisation, l'étape suivante consiste à sérialiser réellement l'objet. Cela se fait en appelant la writeObject()méthode de la java.io.ObjectOutputStreamclasse, comme indiqué dans le Listing 2.

Listing 2. Appel de 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(); } 

Le listing 2 stocke l'état de l' TestSerialobjet dans un fichier appelé temp.out. oos.writeObject(ts);lance en fait l'algorithme de sérialisation, qui à son tour écrit l'objet dans temp.out.

Pour recréer l'objet à partir du fichier persistant, vous utiliseriez le code du Listing 3.

Listing 3. Recréer un objet sérialisé

 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); } 

Dans le Listing 3, la restauration de l'objet se produit avec l' oin.readObject()appel de méthode. Cet appel de méthode lit les octets bruts que nous avons précédemment persistés et crée un objet actif qui est une réplique exacte du graphique d'objet d'origine. Étant donné que vous readObject()pouvez lire n'importe quel objet sérialisable, une conversion au type correct est requise.

L'exécution de ce code imprimera version=100sur la sortie standard.

Le format sérialisé d'un objet

À quoi ressemble la version sérialisée de l'objet? N'oubliez pas que l'exemple de code de la section précédente a enregistré la version sérialisée de l' TestSerialobjet dans le fichier temp.out. Le listing 4 montre le contenu de temp.out, affiché en hexadécimal. (Vous avez besoin d'un éditeur hexadécimal pour voir la sortie au format hexadécimal.)

Listing 4. Forme hexadécimale de TestSerial

 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 

Si vous regardez à nouveau l' TestSerialobjet réel , vous verrez qu'il n'a que deux membres octets, comme indiqué dans le listing 5.

Liste 5. Membres d'octets de TestSerial

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

La taille d'une variable d'octet est d'un octet et, par conséquent, la taille totale de l'objet (sans l'en-tête) est de deux octets. Mais si vous regardez la taille de l'objet sérialisé dans le Listing 4, vous verrez 51 octets. Surprise! D'où viennent les octets supplémentaires et quelle est leur signification? Ils sont introduits par l'algorithme de sérialisation et sont nécessaires pour recréer l'objet. Dans la section suivante, vous explorerez cet algorithme en détail.

Algorithme de sérialisation de Java

À présent, vous devriez avoir une assez bonne connaissance de la sérialisation d'un objet. Mais comment fonctionne le processus sous le capot? En général, l'algorithme de sérialisation effectue les opérations suivantes:

  • Il écrit les métadonnées de la classe associée à une instance.
  • Il écrit récursivement la description de la superclasse jusqu'à ce qu'il la trouve java.lang.object.
  • Une fois qu'il a fini d'écrire les informations de métadonnées, il commence alors avec les données réelles associées à l'instance. Mais cette fois, ça part de la superclasse la plus élevée.
  • Il écrit de manière récursive les données associées à l'instance, de la moins superclasse à la classe la plus dérivée.

J'ai écrit un objet d'exemple différent pour cette section qui couvrira tous les cas possibles. Le nouvel exemple d'objet à sérialiser est présenté dans le Listing 6.

Listing 6. Exemple d'objet sérialisé

 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(); } } 

Cet exemple est simple. Il sérialise un objet de type SerialTest, qui est dérivée de parentet a un objet conteneur, contain. Le format sérialisé de cet objet est indiqué dans le Listing 7.

Listing 7. Forme sérialisée de l'exemple d'objet

 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 

La figure 2 offre un aperçu de haut niveau de l'algorithme de sérialisation pour ce scénario.

Figure 2. Un aperçu de l'algorithme de sérialisation

Passons en revue le format sérialisé de l'objet en détail et voyons ce que chaque octet représente. Commencez par les informations du protocole de sérialisation:

  • AC ED: STREAM_MAGIC. Spécifie qu'il s'agit d'un protocole de sérialisation.
  • 00 05: STREAM_VERSION. La version de sérialisation.
  • 0x73: TC_OBJECT. Spécifie qu'il s'agit d'un nouveau 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 a plus de quatre ans d'expérience dans l'industrie informatique et travaille avec les technologies liées à Java depuis plus de trois ans. Actuellement, il travaille comme ingénieur logiciel système au Java Technology Center, IBM Labs. Il a également une expérience dans l'industrie des télécommunications.

Ressources

  • Lisez la spécification de sérialisation d'objets Java. (Spec est un PDF.)
  • «Aplatissez vos objets: découvrez les secrets de l'API de sérialisation Java» (Todd M. Greanier, JavaWorld, juillet 2000) propose un aperçu des rouages ​​du processus de sérialisation.
  • Le chapitre 10 de Java RMI (William Grosso, O'Reilly, octobre 2001) est également une référence utile.

Cette histoire, "L'algorithme de sérialisation Java révélé" a été publiée à l'origine par JavaWorld.