Svelato l'algoritmo di serializzazione Java

La serializzazione è il processo di salvataggio dello stato di un oggetto in una sequenza di byte; la deserializzazione è il processo di ricostruzione di quei byte in un oggetto attivo. L'API di serializzazione Java fornisce un meccanismo standard agli sviluppatori per gestire la serializzazione degli oggetti. In questo suggerimento, vedrai come serializzare un oggetto e perché a volte è necessaria la serializzazione. Imparerai a conoscere l'algoritmo di serializzazione utilizzato in Java e vedrai un esempio che illustra il formato serializzato di un oggetto. Quando hai finito, dovresti avere una solida conoscenza di come funziona l'algoritmo di serializzazione e quali entità sono serializzate come parte dell'oggetto a un livello basso.

Perché è necessaria la serializzazione?

Nel mondo di oggi, una tipica applicazione aziendale avrà più componenti e sarà distribuita su vari sistemi e reti. In Java, tutto è rappresentato come oggetti; se due componenti Java vogliono comunicare tra loro, è necessario un meccanismo per lo scambio di dati. Un modo per ottenere ciò è definire il proprio protocollo e trasferire un oggetto. Ciò significa che il destinatario deve conoscere il protocollo utilizzato dal mittente per ricreare l'oggetto, il che renderebbe molto difficile parlare con componenti di terze parti. Quindi, ci deve essere un protocollo generico ed efficiente per trasferire l'oggetto tra i componenti. La serializzazione è definita a questo scopo e i componenti Java utilizzano questo protocollo per trasferire gli oggetti.

La Figura 1 mostra una vista di alto livello della comunicazione client / server, in cui un oggetto viene trasferito dal client al server tramite serializzazione.

Figura 1. Una vista di alto livello della serializzazione in azione (fare clic per ingrandire)

Come serializzare un oggetto

Per serializzare un oggetto, è necessario assicurarsi che la classe dell'oggetto implementi l' java.io.Serializableinterfaccia, come mostrato nel Listato 1.

Listato 1. Implementazione di Serializable

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

Nel listato 1, l'unica cosa che dovevi fare diversamente dalla creazione di una normale classe era implementare l' java.io.Serializableinterfaccia. L' Serializableinterfaccia è un'interfaccia marker; non dichiara affatto metodi. Indica al meccanismo di serializzazione che la classe può essere serializzata.

Ora che hai reso la classe idonea per la serializzazione, il passaggio successivo consiste nel serializzare effettivamente l'oggetto. Ciò viene fatto chiamando il writeObject()metodo della java.io.ObjectOutputStreamclasse, come mostrato nel Listato 2.

Listato 2. Chiamare 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(); } 

Il Listato 2 memorizza lo stato TestSerialdell'oggetto in un file chiamato temp.out. oos.writeObject(ts);in realtà dà il via all'algoritmo di serializzazione, che a sua volta scrive l'oggetto temp.out.

Per ricreare l'oggetto dal file persistente, dovresti utilizzare il codice nel Listato 3.

Listato 3. Ricreazione di un oggetto serializzato

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

Nel Listato 3, il ripristino dell'oggetto avviene con la oin.readObject()chiamata al metodo. Questa chiamata al metodo legge i byte grezzi che abbiamo precedentemente mantenuto e crea un oggetto live che è una replica esatta dell'oggetto grafico originale. Poiché readObject()può leggere qualsiasi oggetto serializzabile, è richiesto un cast al tipo corretto.

L'esecuzione di questo codice verrà stampato version=100sullo standard output.

Il formato serializzato di un oggetto

Che aspetto ha la versione serializzata dell'oggetto? Ricorda, il codice di esempio nella sezione precedente ha salvato la versione serializzata TestSerialdell'oggetto nel file temp.out. Il Listato 4 mostra il contenuto di temp.out, visualizzato in esadecimale. (È necessario un editor esadecimale per visualizzare l'output in formato esadecimale.)

Listato 4. Forma esadecimale di 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 

Se guardi di nuovo l' TestSerialoggetto reale , vedrai che ha solo due membri di byte, come mostrato nel Listato 5.

Listato 5. Membri byte di TestSerial

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

La dimensione di una variabile di byte è un byte e quindi la dimensione totale dell'oggetto (senza l'intestazione) è di due byte. Ma se guardi la dimensione dell'oggetto serializzato nel Listato 4, vedrai 51 byte. Sorpresa! Da dove provengono i byte extra e qual è il loro significato? Sono introdotti dall'algoritmo di serializzazione e sono necessari per ricreare l'oggetto. Nella sezione successiva, esplorerai questo algoritmo in dettaglio.

Algoritmo di serializzazione di Java

A questo punto, dovresti avere una conoscenza abbastanza buona di come serializzare un oggetto. Ma come funziona il processo sotto il cofano? In generale, l'algoritmo di serializzazione esegue le seguenti operazioni:

  • Scrive i metadati della classe associata a un'istanza.
  • Scrive ricorsivamente la descrizione della superclasse finché non la trova java.lang.object.
  • Una volta terminata la scrittura delle informazioni sui metadati, inizia con i dati effettivi associati all'istanza. Ma questa volta inizia dalla superclasse più alta.
  • Scrive in modo ricorsivo i dati associati all'istanza, partendo dalla meno superclasse fino alla classe più derivata.

Ho scritto un oggetto di esempio diverso per questa sezione che coprirà tutti i casi possibili. Il nuovo oggetto di esempio da serializzare è mostrato nel Listato 6.

Listato 6. Esempio di oggetto serializzato

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

Questo esempio è semplice. Si serializza un oggetto di tipo SerialTest, che è derivato da parente ha un oggetto contenitore, contain. Il formato serializzato di questo oggetto è mostrato nel Listato 7.

Listato 7. Forma serializzata di un oggetto campione

 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 offre uno sguardo di alto livello all'algoritmo di serializzazione per questo scenario.

Figura 2. Schema dell'algoritmo di serializzazione

Esaminiamo in dettaglio il formato serializzato dell'oggetto e vediamo cosa rappresenta ogni byte. Inizia con le informazioni sul protocollo di serializzazione:

  • AC ED: STREAM_MAGIC. Specifica che questo è un protocollo di serializzazione.
  • 00 05: STREAM_VERSION. La versione di serializzazione.
  • 0x73: TC_OBJECT. Specifica che si tratta di un nuovo 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 ha più di quattro anni di esperienza nel settore IT e lavora con le tecnologie relative a Java da più di tre anni. Attualmente lavora come ingegnere del software di sistema presso il Java Technology Center, IBM Labs. Ha anche esperienza nel settore delle telecomunicazioni.

Risorse

  • Leggere la specifica di serializzazione degli oggetti Java. (La specifica è un PDF.)
  • "Appiattisci i tuoi oggetti: scopri i segreti dell'API di serializzazione Java" (Todd M. Greanier, JavaWorld, luglio 2000) offre uno sguardo ai dettagli del processo di serializzazione.
  • Anche il capitolo 10 di Java RMI (William Grosso, O'Reilly, ottobre 2001) è un utile riferimento.

Questa storia, "L'algoritmo di serializzazione Java rivelato" è stata originariamente pubblicata da JavaWorld.