Programování

Algoritmus serializace Java odhalen

Serializace je proces ukládání stavu objektu do sekvence bajtů; deserializace je proces přestavby těchto bytů na živý objekt. Rozhraní API Java Serialization poskytuje vývojářům standardní mechanismus pro zpracování serializace objektů. V tomto tipu uvidíte, jak serializovat objekt a proč je někdy nutná serializace. Dozvíte se o algoritmu serializace používaném v Javě a uvidíte příklad, který ilustruje serializovaný formát objektu. V době, kdy jste hotovi, byste měli mít solidní znalosti o tom, jak serializační algoritmus funguje a jaké entity jsou serializovány jako součást objektu na nízké úrovni.

Proč je vyžadována serializace?

V dnešním světě bude mít typická podniková aplikace více komponent a bude distribuována do různých systémů a sítí. V Javě je vše reprezentováno jako objekty; pokud dvě komponenty Java chtějí spolu komunikovat, musí existovat mechanismus pro výměnu dat. Jedním ze způsobů, jak toho dosáhnout, je definovat vlastní protokol a přenést objekt. To znamená, že přijímací konec musí znát protokol používaný odesílatelem k opětovnému vytvoření objektu, což by velmi zkomplikovalo komunikaci s komponentami třetích stran. Proto k přenosu objektu mezi komponentami musí existovat obecný a efektivní protokol. Za tímto účelem je definována serializace a komponenty Java používají tento protokol k přenosu objektů.

Obrázek 1 ukazuje pohled na vysokou úroveň komunikace mezi klientem a serverem, kdy je objekt přenášen z klienta na server prostřednictvím serializace.

Obrázek 1. Pohled na vysokou úroveň serializace v akci (kliknutím ji zvětšíte)

Jak serializovat objekt

Chcete-li serializovat objekt, musíte zajistit, aby třída objektu implementovala java.io. Serializovatelné rozhraní, jak je uvedeno v seznamu 1.

Výpis 1. Implementace Serializovatelné

 import java.io.Serializable; třída TestSerial implementuje Serializable {public byte version = 100; počet veřejných bajtů = 0; } 

V seznamu 1 je jediná věc, kterou jste museli udělat jinak než při vytváření normální třídy, implementace java.io. Serializovatelné rozhraní. The Serializovatelné interface je značkovací rozhraní; deklaruje vůbec žádné metody. Říká mechanismu serializace, že třídu lze serializovat.

Nyní, když jste udělali třídu vhodnou pro serializaci, je dalším krokem skutečně serializovat objekt. To se provádí voláním writeObject () metoda java.io.ObjectOutputStream třídy, jak je uvedeno v seznamu 2.

Výpis 2. Volání writeObject ()

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

Výpis 2 ukládá stav souboru Testovací seriál objekt v souboru s názvem temp.out. oos.writeObject (ts); ve skutečnosti odstartuje serializační algoritmus, který následně zapíše objekt temp.out.

Chcete-li objekt znovu vytvořit z trvalého souboru, použijete kód uvedený v seznamu 3.

Výpis 3. Opětovné vytvoření serializovaného objektu

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

V seznamu 3 dochází k obnově objektu pomocí oin.readObject () volání metody. Toto volání metody čte v surových bajtech, které jsme dříve přetrvávali, a vytvoří živý objekt, který je přesnou replikou grafu původního objektu. Protože readObject () umí číst jakýkoli serializovatelný objekt, je vyžadován cast na správný typ.

Po provedení tohoto kódu se vytiskne verze = 100 na standardním výstupu.

Serializovaný formát objektu

Jak vypadá serializovaná verze objektu? Nezapomeňte, že ukázkový kód v předchozí části uložil serializovanou verzi Testovací seriál objekt do souboru temp.out. Výpis 4 zobrazuje obsah temp.out, zobrazeno šestnáctkově. (K zobrazení výstupu v hexadecimálním formátu potřebujete šestnáctkový editor.)

Výpis 4. Šestnáctková forma 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 

Pokud se znovu podíváte na skutečné Testovací seriál objektu, uvidíte, že má pouze dva bajtové členy, jak je uvedeno v seznamu 5.

Výpis 5. Členové bajtu TestSerial

 verze veřejného bajtu = 100; počet veřejných bajtů = 0; 

Velikost bajtové proměnné je jeden bajt, a tedy celková velikost objektu (bez záhlaví) jsou dva bajty. Pokud se ale podíváte na velikost serializovaného objektu v seznamu 4, uvidíte 51 bajtů. Překvapení! Odkud pocházejí další bajty a jaký je jejich význam? Jsou zavedeny algoritmem serializace a jsou nutné k opětovnému vytvoření objektu. V další části podrobně prozkoumáte tento algoritmus.

Algoritmus serializace Java

Nyní byste měli mít docela dobrou znalost toho, jak serializovat objekt. Jak ale proces probíhá pod kapotou? Obecně algoritmus serializace provádí následující:

  • Vypíše metadata třídy přidružené k instanci.
  • Rekurzivně zapisuje popis nadtřídy, dokud ji nenajde java.lang.object.
  • Jakmile dokončí psaní informací metadat, začne skutečnými daty přidruženými k instanci. Tentokrát to ale začíná od nejvyšší nadtřídy.
  • Rekurzivně zapisuje data spojená s instancí, počínaje od nejméně nadtřídy po nejodvozenější třídu.

Pro tuto část jsem napsal jiný ukázkový objekt, který pokryje všechny možné případy. Nový ukázkový objekt, který má být serializován, je uveden v seznamu 6.

Výpis 6. Ukázkový serializovaný objekt

 class rodič implementuje Serializable {int parentVersion = 10; } třída obsahuje nářadí Serializovatelná {int containVersion = 11; } public class SerialTest extends parent implements Serializable {int version = 66; obsahovat con = new obsahovat (); public int getVersion () {návratová verze; } public static void main (String args []) hodí IOException {FileOutputStream fos = nový FileOutputStream ("temp.out"); ObjectOutputStream oos = nový ObjectOutputStream (fos); SerialTest st = nový SerialTest (); oos.writeObject (st); oos.flush (); oos.close (); }} 

Tento příklad je přímý. Serializuje objekt typu SerialTest, který je odvozen od rodič a má kontejnerový objekt, obsahovat. Serializovaný formát tohoto objektu je uveden v seznamu 7.

Výpis 7. Serializovaná forma ukázkového objektu

 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 

Obrázek 2 nabízí na vysoké úrovni pohled na algoritmus serializace pro tento scénář.

Obrázek 2. Nástin serializačního algoritmu

Pojďme si podrobně projít serializovaným formátem objektu a podívejme se, co každý bajt představuje. Začněte informacemi protokolu serializace:

  • AC ED: STREAM_MAGIC. Určuje, že se jedná o protokol serializace.
  • 00 05: STREAM_VERSION. Verze serializace.
  • 0x73: TC_OBJECT. Určuje, že se jedná o nový Objekt.

Prvním krokem algoritmu serializace je napsat popis třídy přidružené k instanci. Příklad serializuje objekt typu SerialTest, takže algoritmus začíná napsáním popisu souboru SerialTest třída.

  • 0x72: TC_CLASSDESC. Určuje, že se jedná o novou třídu.
  • 00 0A: Délka názvu třídy.
  • 53 65 72 69 61 6c 54 65 73 74: SerialTest, název třídy.
  • 05 52 81 5A AC 66 02 F6: SerialVersionUID, identifikátor sériové verze této třídy.
  • 0x02: Různé vlajky. Tento konkrétní příznak říká, že objekt podporuje serializaci.
  • 00 02: Počet polí v této třídě.

Dále algoritmus zapíše pole int verze = 66;.

  • 0x49: Kód typu pole. 49 představuje „I“, což znamená Int.
  • 00 07: Délka názvu pole.
  • 76 65 72 73 69 6F 6E: verze, název pole.

A pak algoritmus zapíše další pole, obsahovat con = new obsahovat ();. Toto je objekt, takže zapíše kanonický podpis JVM tohoto pole.

  • 0x74: TC_STRING. Představuje nový řetězec.
  • 00 09: Délka řetězce.
  • 4C 63 6F 6E 74 61 69 6E 3B: Lobsah;, kanonický podpis JVM.
  • 0x78: TC_ENDBLOCKDATA, konec volitelných dat bloku pro objekt.

Dalším krokem algoritmu je napsat popis rodič třída, což je bezprostřední nadtřída třídy SerialTest.

  • 0x72: TC_CLASSDESC. Určuje, že se jedná o novou třídu.
  • 00 06: Délka názvu třídy.
  • 70 61 72 65 6E 74: SerialTest, název třídy
  • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID, identifikátor sériové verze této třídy.
  • 0x02: Různé vlajky. Tento příznak uvádí, že objekt podporuje serializaci.
  • 00 01: Počet polí v této třídě.

Nyní algoritmus zapíše popis pole pro rodič třída. rodič má jedno pole, int parentVersion = 100;.

  • 0x49: Kód typu pole. 49 představuje „I“, což znamená Int.
  • 00 0D: Délka názvu pole.
  • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion, název pole.
  • 0x78: TC_ENDBLOCKDATA, konec dat bloku pro tento objekt.
  • 0x70: TC_NULL, což představuje skutečnost, že již neexistují žádné nadtřídy, protože jsme dosáhli vrcholu hierarchie tříd.

Algoritmus pro serializaci dosud napsal popis třídy přidružené k instanci a všechny její nadtřídy. Dále zapíše skutečná data přidružená k instanci. Nejprve zapíše členy nadřazené třídy:

  • 00 00 00 0A: 10, hodnota parentVersion.

Poté přejde na SerialTest.

  • 00 00 00 42: 66, hodnota verze.

Dalších několik bajtů je zajímavých. Algoritmus musí zapsat informace o obsahovat objekt zobrazený v seznamu 8.

Výpis 8. Objekt obsahovat

 obsahovat con = new obsahovat (); 

Nezapomeňte, že algoritmus serializace nenapsal popis třídy pro obsahovat třída ještě. Toto je příležitost napsat tento popis.

  • 0x73: TC_OBJECT, označující nový objekt.
  • 0x72: TC_CLASSDESC.
  • 00 07: Délka názvu třídy.
  • 63 6F 6E 74 61 69 6E: obsahovat, název třídy.
  • FC BB E6 0E FB CB 60 C7: SerialVersionUID, identifikátor sériové verze této třídy.
  • 0x02: Různé vlajky. Tento příznak označuje, že tato třída podporuje serializaci.
  • 00 01: Počet polí v této třídě.

Dále musí algoritmus napsat popis pro obsahovatjediné pole, int containVersion = 11;.

  • 0x49: Kód typu pole. 49 představuje „I“, což znamená Int.
  • 00 0E: Délka názvu pole.
  • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: obsahovatVerzi, název pole.
  • 0x78: TC_ENDBLOCKDATA.

Dále algoritmus serializace zkontroluje, zda obsahovat má nějaké nadřazené třídy. Pokud ano, algoritmus by začal psát tuto třídu; ale v tomto případě neexistuje žádná nadtřída pro obsahovat, takže algoritmus zapisuje TC_NULL.

  • 0x70: TC_NULL.

Nakonec algoritmus zapíše skutečná data spojená s obsahovat.

  • 00 00 00 0B: 11, hodnota obsahovatVerzi.

Závěr

V tomto tipu jste viděli, jak serializovat objekt, a zjistili jste, jak serializační algoritmus funguje podrobně. Doufám, že tento článek vám poskytne více podrobností o tom, co se stane, když skutečně serializujete objekt.

O autorovi

Sathiskumar Palaniappan má více než čtyři roky zkušeností v IT průmyslu a více než tři roky pracuje s technologiemi souvisejícími s Java. V současné době pracuje jako systémový softwarový inženýr v Java Technology Center, IBM Labs. Má také zkušenosti v telekomunikačním průmyslu.

Zdroje

  • Přečtěte si specifikaci serializace objektů Java. (Specifikace je PDF.)
  • „Zploštění objektů: Objevte tajemství rozhraní Java Serialization API“ (Todd M. Greanier, JavaWorld, červenec 2000) nabízí pohled na základní myšlenky procesu serializace.
  • Kapitola 10 Java RMI (William Grosso, O'Reilly, říjen 2001) je také užitečným odkazem.

Tento příběh, „Byl odhalen algoritmus serializace Javy“, původně publikoval JavaWorld.