El algoritmo de serialización de Java revelado

La serialización es el proceso de guardar el estado de un objeto en una secuencia de bytes; La deserialización es el proceso de reconstrucción de esos bytes en un objeto activo. La API de serialización de Java proporciona un mecanismo estándar para que los desarrolladores manejen la serialización de objetos. En este consejo, verá cómo serializar un objeto y por qué a veces es necesaria la serialización. Aprenderá sobre el algoritmo de serialización utilizado en Java y verá un ejemplo que ilustra el formato serializado de un objeto. Cuando haya terminado, debe tener un conocimiento sólido de cómo funciona el algoritmo de serialización y qué entidades se serializan como parte del objeto en un nivel bajo.

¿Por qué se requiere la serialización?

En el mundo actual, una aplicación empresarial típica tendrá varios componentes y se distribuirá en varios sistemas y redes. En Java, todo se representa como objetos; si dos componentes de Java quieren comunicarse entre sí, es necesario que exista un mecanismo para intercambiar datos. Una forma de lograrlo es definir su propio protocolo y transferir un objeto. Esto significa que el extremo receptor debe conocer el protocolo utilizado por el remitente para volver a crear el objeto, lo que dificultaría mucho la comunicación con componentes de terceros. Por tanto, es necesario que exista un protocolo genérico y eficaz para transferir el objeto entre componentes. La serialización se define para este propósito, y los componentes de Java utilizan este protocolo para transferir objetos.

La Figura 1 muestra una vista de alto nivel de la comunicación cliente / servidor, donde un objeto se transfiere del cliente al servidor a través de la serialización.

Figura 1. Una vista de alto nivel de la serialización en acción (haga clic para ampliar)

Cómo serializar un objeto

Para serializar un objeto, debe asegurarse de que la clase del objeto implemente la java.io.Serializableinterfaz, como se muestra en el Listado 1.

Listado 1. Implementando serializable

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

En el Listado 1, lo único que tenía que hacer de manera diferente a la creación de una clase normal es implementar la java.io.Serializableinterfaz. La Serializableinterfaz es una interfaz de marcador; no declara ningún método. Le dice al mecanismo de serialización que la clase se puede serializar.

Ahora que ha hecho que la clase sea elegible para la serialización, el siguiente paso es serializar el objeto. Eso se hace llamando al writeObject()método de la java.io.ObjectOutputStreamclase, como se muestra en el Listado 2.

Listado 2. Llamar a 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(); } 

El Listado 2 almacena el estado del TestSerialobjeto en un archivo llamado temp.out. oos.writeObject(ts);de hecho, inicia el algoritmo de serialización, que a su vez escribe el objeto en temp.out.

Para volver a crear el objeto a partir del archivo persistente, debe emplear el código del Listado 3.

Listado 3. Recreando un objeto serializado

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

En el Listado 3, la restauración del objeto ocurre con la oin.readObject()llamada al método. Esta llamada al método lee los bytes sin procesar que persistimos previamente y crea un objeto en vivo que es una réplica exacta del gráfico del objeto original. Debido a que readObject()puede leer cualquier objeto serializable, se requiere una conversión al tipo correcto.

La ejecución de este código se imprimirá version=100en la salida estándar.

El formato serializado de un objeto

¿Cómo se ve la versión serializada del objeto? Recuerde, el código de muestra de la sección anterior guardó la versión serializada del TestSerialobjeto en el archivo temp.out. El Listado 4 muestra el contenido de temp.out, mostrado en hexadecimal. (Necesita un editor hexadecimal para ver la salida en formato hexadecimal).

Listado 4. Forma hexadecimal 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 vuelve a mirar el TestSerialobjeto real , verá que solo tiene dos bytes de miembros, como se muestra en el Listado 5.

Listado 5. Miembros de bytes de TestSerial

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

El tamaño de una variable de byte es de un byte y, por lo tanto, el tamaño total del objeto (sin el encabezado) es de dos bytes. Pero si observa el tamaño del objeto serializado en el Listado 4, verá 51 bytes. ¡Sorpresa! ¿De dónde provienen los bytes adicionales y cuál es su significado? Son introducidos por el algoritmo de serialización y son necesarios para volver a crear el objeto. En la siguiente sección, explorará este algoritmo en detalle.

Algoritmo de serialización de Java

A estas alturas, debería tener un conocimiento bastante bueno de cómo serializar un objeto. Pero, ¿cómo funciona el proceso bajo el capó? En general, el algoritmo de serialización hace lo siguiente:

  • Escribe los metadatos de la clase asociada con una instancia.
  • Escribe de forma recursiva la descripción de la superclase hasta que encuentra java.lang.object.
  • Una vez que termina de escribir la información de metadatos, comienza con los datos reales asociados con la instancia. Pero esta vez, comienza desde la superclase más alta.
  • Escribe de forma recursiva los datos asociados con la instancia, comenzando desde la superclase menor hasta la clase más derivada.

He escrito un objeto de ejemplo diferente para esta sección que cubrirá todos los casos posibles. El nuevo objeto de muestra que se va a serializar se muestra en el Listado 6.

Listado 6. Objeto serializado de muestra

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

Este ejemplo es sencillo. Se serializa un objeto de tipo SerialTest, que se deriva de parenty tiene un objeto contenedor, contain. El formato serializado de este objeto se muestra en el Listado 7.

Listado 7. Forma serializada de objeto de muestra

 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 Figura 2 ofrece una visión de alto nivel del algoritmo de serialización para este escenario.

Figura 2. Esquema del algoritmo de serialización

Repasemos el formato serializado del objeto en detalle y veamos qué representa cada byte. Comience con la información del protocolo de serialización:

  • AC ED: STREAM_MAGIC. Especifica que se trata de un protocolo de serialización.
  • 00 05: STREAM_VERSION. La versión de serialización.
  • 0x73: TC_OBJECT. Especifica que se trata de un nuevo Object.

El primer paso del algoritmo de serialización es escribir la descripción de la clase asociada con una instancia. El ejemplo serializa un objeto de tipo SerialTest, por lo que el algoritmo comienza escribiendo la descripción de la SerialTestclase.

  • 0x72: TC_CLASSDESC. Especifica que esta es una nueva clase.
  • 00 0A: Longitud del nombre de la clase.
  • 53 65 72 69 61 6c 54 65 73 74: SerialTest, el nombre de la clase.
  • 05 52 81 5A AC 66 02 F6: SerialVersionUID, el identificador de la versión de serie de esta clase.
  • 0x02: Varias banderas. Esta bandera en particular dice que el objeto admite la serialización.
  • 00 02: Número de campos de esta clase.

A continuación, el algoritmo escribe el campo int version = 66;.

  • 0x49: Código de tipo de campo. 49 representa "yo", que significa Int.
  • 00 07: Longitud del nombre del campo.
  • 76 65 72 73 69 6F 6E: version, el nombre del campo.

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 tiene más de cuatro años de experiencia en la industria de TI y ha estado trabajando con tecnologías relacionadas con Java durante más de tres años. Actualmente, trabaja como ingeniero de software de sistemas en Java Technology Center, IBM Labs. También tiene experiencia en la industria de las telecomunicaciones.

Recursos

  • Lea la especificación de serialización de objetos Java. (La especificación es un PDF).
  • "Aplanar sus objetos: descubra los secretos de la API de serialización de Java" (Todd M. Greanier, JavaWorld, julio de 2000) ofrece una mirada a los aspectos prácticos del proceso de serialización.
  • El capítulo 10 de Java RMI (William Grosso, O'Reilly, octubre de 2001) también es una referencia útil.

Esta historia, "Revelado el algoritmo de serialización de Java" fue publicada originalmente por JavaWorld.