CodeGym /Java Blog /Random /Serialization at deserialization sa Java
John Squirrels
Antas
San Francisco

Serialization at deserialization sa Java

Nai-publish sa grupo
Hi! Sa aralin ngayon, pag-uusapan natin ang tungkol sa serialization at deserialization sa Java. Magsisimula tayo sa isang simpleng halimbawa. Isipin na ikaw ay isang computer game developer. Kung lumaki ka noong 90s at naaalala ang mga game console noong panahong iyon, malamang na alam mo na kulang sila sa isang bagay na pinababayaan natin ngayon — ang kakayahang mag-save at mag-load ng mga laro :) Kung hindi, isipin iyon!Serialization at deserialization sa Java - 1Natatakot ako na ang larong walang ganitong mga kakayahan ngayon ay mapapahamak! Anyway, ano ang ibig sabihin ng 'mag-save' at 'mag-load' ng laro? Well, naiintindihan namin ang pang-araw-araw na kahulugan: gusto naming ipagpatuloy ang laro mula sa lugar kung saan kami tumigil. Upang gawin ito, lumikha kami ng isang tiyak na 'check point' na gagamitin namin sa ibang pagkakataon upang i-load ang laro. Ngunit ano ang ibig sabihin nito sa isang programmer sa halip na isang kaswal na gamer? Ang sagot ay simple: nai-save namin ang estado ng aming programa. Sabihin nating naglalaro ka ng Spain sa isang larong diskarte. Ang iyong laro ay may estado: kung aling mga teritoryo mayroon ang lahat, kung gaano karaming mga mapagkukunan ang mayroon ang lahat, anong mga alyansa ang umiiral at kung kanino, sino ang nasa digmaan, at iba pa. Ang impormasyong ito, ang estado ng aming programa, ay dapat na kahit papaano ay mai-save upang maibalik ang data at maipagpatuloy ang laro. Sa nangyayari, Ang serialization sa Java ay ang proseso ng pag-save ng estado ng isang bagay bilang isang sequence ng mga byte. Ang deserialization sa Java ay ang proseso ng pagpapanumbalik ng isang bagay mula sa mga byte na ito. Anumang Java object ay maaaring ma-convert sa isang byte sequence. Bakit kailangan natin ang mga ito? Paulit-ulit naming sinabi na ang mga programa ay hindi umiiral nang mag-isa. Kadalasan, nakikipag-ugnayan sila sa isa't isa, nagpapalitan ng data, atbp. Ang isang byte na format ay maginhawa at mahusay para dito. Halimbawa, maaari naming i-convert ang isang bagay ng aming SavedGameklase sa isang pagkakasunud-sunod ng mga byte, ilipat ang mga byte na ito sa network sa isa pang computer, at pagkatapos ay sa kabilang computer, i-convert ang mga byte na ito pabalik sa isang Java object! Mukhang mahirap, ha? Mukhang magiging mahirap gawin ang lahat ng ito: / Sa kabutihang palad, hindi iyon ang kaso! :) Sa Java, ang Serializable interface ay responsable para sa proseso ng serialization. Napakasimple ng interface na ito: Hindi mo kailangang magpatupad ng isang paraan para magamit ito! Tingnan kung gaano kasimple ang aming klase para sa pag-save ng mga laro:

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoryInfo;
   private String[] resourceInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoryInfo, String[] resourceInfo, String[] diplomacyInfo){
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(String[] territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public String[] getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(String[] resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + Arrays.toString(territoryInfo) +
               ", resourceInfo=" + Arrays.toString(resourceInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
Tatlong array ang may pananagutan para sa impormasyon tungkol sa mga teritoryo, mapagkukunan, at diplomasya, at ang Serializable na interface ay nagsasabi sa Java machine: ' lahat ay maayos kung ang mga bagay ng klase na ito ay maaaring serialized '. Ang isang interface na walang isang interface ay mukhang kakaiba :/ Bakit ito kinakailangan? Ang sagot sa tanong na iyon ay ibinigay sa itaas: ito ay kinakailangan lamang upang magbigay ng kinakailangang impormasyon sa Java machine. Sa isang nakaraang aralin, maikling binanggit namin ang mga interface ng marker. Ito ay mga espesyal na interface ng impormasyon na nagmamarka lamang sa aming mga klase ng karagdagang impormasyon na magiging kapaki-pakinabang sa Java machine sa hinaharap. Wala silang anumang mga pamamaraan na kailangan mong ipatupad. Narito ang Serializable — isang ganoong interface. Narito ang isa pang mahalagang punto: Bakit kailangan natin angpribadong static final long serialVersionUID variable na tinukoy namin sa klase? Ang field na ito ay naglalaman ng natatanging identifier ng bersyon ng serialized na klase. Ang bawat klase na nagpapatupad ng Serializable na interface ay may identifier ng bersyon. Ito ay tinutukoy batay sa nilalaman ng klase — mga patlang at ang kanilang pagkakasunud-sunod ng deklarasyon, at mga pamamaraan at ang kanilang pagkakasunud-sunod ng deklarasyon. At kung babaguhin namin ang isang uri ng field at/o ang bilang ng mga field sa aming klase, agad na nagbabago ang identifier ng bersyon. Ang serialVersionUID ay isinusulat din kapag ang klase ay serialized. Kapag sinubukan naming i-deserialize, ibig sabihin, ibalik ang isang bagay mula sa isang byte sequence, ang halaga ng serialVersionUID ay inihambing sa halaga ng serialVersionUIDng klase sa aming programa. Kung hindi tumutugma ang mga value, itatapon ang isang java.io.InvalidClassException. Makakakita tayo ng isang halimbawa nito sa ibaba. Upang maiwasan ang mga ganitong sitwasyon, itinakda lang namin nang manu-mano ang identifier ng bersyon para sa aming klase. Sa aming kaso, ito ay magiging katumbas lamang ng 1 (maaari mong gamitin ang anumang iba pang numero na gusto mo). Well, oras na para subukang i-serialize ang aming SavedGame object at tingnan kung ano ang mangyayari!

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourceInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and release resources
       objectOutputStream.close();
   }
}
Gaya ng nakikita mo, gumawa kami ng 2 stream: FileOutputStream at ObjectOutputStream . Alam ng una kung paano magsulat ng data sa file, at ang pangalawa ay nagko-convert ng mga bagay sa mga byte. Nakakita ka na ng mga katulad na nested construct, halimbawa, new BufferedReader(new InputStreamReader(...)) , sa mga nakaraang lesson, kaya hindi ka dapat matakot :) Sa pamamagitan ng paggawa ng chain na ito ng dalawang stream, ginagawa namin ang parehong gawain: kino-convert namin ang SavedGame object sa isang sequence ng mga byte at i-save ito sa isang file gamit ang writeObject() method. At oo nga pala, hindi man lang namin tiningnan kung ano ang nakuha namin! Oras na para tingnan ang file! *Tandaan: hindi kinakailangan na gumawa ng file nang maaga. Kung walang file na may tinukoy na pangalan, awtomatiko itong malilikha* At narito ang mga nilalaman nito: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang. String;¬ТVзй{G xp t pPTL РёС†Rusyo етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 РеРусский руский Рё 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt% №t &РЈ Франции 8 провинций Oh, oh :( Mukhang hindi gumana ang program namin : ( Actually, gumana ito. Naaalala mo na nagpadala kami ng sequence ng byte, at hindi lang isang object o text, sa file? Well, ito ang byte sequence na ito. Mukhang :) Ito ang aming na-save na laro! Kung gusto naming ibalik ang aming orihinal na bagay, ibig sabihin, simulan at ipagpatuloy ang laro kung saan kami tumigil, kailangan namin ang reverse na proseso: deserialization. Ito ang hitsura para sa amin:

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);
   }
}
At narito ang resulta! SavedGame{territoryInfo=[May 6 na probinsya ang Spain, 10 provinces ang Russia, 8 provinces ang France], resourceInfo=[May 100 gold ang Spain, 80 gold ang Russia, 90 gold ang France], diplomacyInfo=[Nakipagdigma ang France sa Russia, Nakuha ng Spain ang isang neutral na posisyon]} Magaling! Nagawa muna naming i-save ang estado ng aming laro sa isang file, at pagkatapos ay ibalik ito mula sa file. Ngayon, subukan nating gawin ang parehong bagay, ngunit aalisin namin ang pagkakakilanlan ng bersyon mula sa aming klase ng SavedGame . Hindi namin muling isusulat ang aming mga klase. Magiging pareho ang kanilang code. Aalisin lang namin ang pribadong static na final long serialVersionUID mula sa SavedGame class. Narito ang aming object pagkatapos ng serialization: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранциСЗСЗРА ЃСЃРёРµР№, PL ё 100 золотР°t РЈ Р РсСЃСЃЃРёРё 80 Русский Русский t ! ЂРѕРІРёРЅС†РёР№t %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 првинцийt &РЈ Франции 8 првинцийt &РЈ Франции 8 РїСЂРѕРІР‖РЅС kapag nalaman natin kung ano ang mangyayari : InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747 Oo nga pala, may nakalimutan kaming importante. Malinaw, ang mga string at primitive ay madaling na-serialize: Ang Java ay tiyak na may ilang built-in na mekanismo para dito. Ngunit paano kung ang aming serializable na klase ay may mga field na hindi primitive, ngunit sa halip ay mga sanggunian sa iba pang mga bagay? Halimbawa, gumawa tayo ng hiwalay na TerritoryInfo , ResourceInfo at DiplomacyInfo na klase upang gumana sa aming SavedGame na klase.

public class TerritoryInfo {

   private String info;

   public TerritoryInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoryInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourceInfo {

   private String info;

   public ResourceInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourceInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class DiplomacyInfo {

   private String info;

   public DiplomacyInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "DiplomacyInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}
At ngayon ay nahaharap tayo sa isang tanong: Dapat bang Serializable ang lahat ng klaseng ito kung gusto nating i-serialize ang klase ng SavedGame ?

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoryInfo getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(TerritoryInfo territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public ResourceInfo getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(ResourceInfo resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + territoryInfo +
               ", resourceInfo=" + resourceInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Sige, kung gayon! Subukan natin ito! Sa ngayon, iiwanan namin ang lahat at susubukan naming i-serialize ang isang bagay na SavedGame :

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Resulta: Exception sa thread na "main" java.io.NotSerializableException: DiplomacyInfo Hindi ito gumana! Kaya, narito ang sagot sa aming tanong. Kapag ang isang bagay ay na-serialize, ang lahat ng mga bagay na na-refer ng mga instance na variable nito ay serialized. At kung ang mga bagay na iyon ay tumutukoy din sa iba pang mga bagay, kung gayon ang mga ito ay serialized din. At sa at sa magpakailanman. Ang lahat ng mga klase sa chain na ito ay dapat na Serializable , kung hindi, imposibleng i-serialize ang mga ito at isang exception ang itatapon. Sa pamamagitan ng paraan, maaari itong lumikha ng mga problema sa kalsada. Halimbawa, ano ang dapat nating gawin kung hindi natin kailangan ng bahagi ng isang klase sa panahon ng serialization? O paano kung nakuha namin ang aming klase ng TerritoryInfo 'sa pamamagitan ng mana' bilang bahagi ng isang library? At ipagpalagay pa na ito ay 'at, ayon dito, hindi natin ito mababago. Nangangahulugan iyon na hindi kami makakapagdagdag ng field ng TerritoryInfo sa aming klase ng SavedGame , dahil magiging unserializable ang buong klase ng SavedGame ! Iyan ay isang problema: / Serialization at deserialization sa Java - 2Sa Java, ang ganitong uri ng problema ay nalutas sa pamamagitan ng lumilipas na keyword. Kung idinagdag mo ang keyword na ito sa isang field ng iyong klase, hindi ise-serialize ang field na iyon. Subukan nating gawin ang isa sa mga field ng aming SavedGame class na lumilipas , at pagkatapos ay i-serialize at ire-restore namin ang isang bagay.

import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   // ...getters, setters, toString()...
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
At narito ang resulta: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='May 100 ginto ang Spain, may 80 gold ang Russia, may 90 gold ang France'}, diplomacyInfo=DiplomacyInfo{info='Nakikipagdigma ang France sa Russia, Spain ay kumuha ng neutral na posisyon'}} Sabi nga, nakakuha kami ng sagot sa tanong kung anong halaga ang itatalaga sa isang lumilipas na field. Ito ay itinalaga ang default na halaga. Para sa mga bagay, ito ay null . Mababasa mo ang isang mahusay na kabanata sa paksang ito sa aklat na 'Head-First Java', Bigyang-pansin ito :)
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION