JavaRush /Java Blog /Random-TL /Serialization at Deserialization sa Java

Serialization at Deserialization sa Java

Nai-publish sa grupo
Kamusta! Sa lecture ngayon ay pag-uusapan natin ang tungkol sa serialization at deserialization sa Java. Magsimula tayo sa isang simpleng halimbawa. Sabihin nating ikaw ang lumikha ng isang laro sa computer. Kung lumaki ka noong 90s at naaalala ang mga game console ng mga panahong iyon, malamang na alam mo na kulang sila ng isang bagay na halata ngayon - nagse-save at naglo-load ng mga laro :) Kung hindi... isipin mo! Natatakot ako na ang larong walang ganoong pagkakataon ngayon ay mapapahamak sa kabiguan! At, sa totoo lang, ano ang "nagse-save" at "naglo-load" ng isang laro? Well, sa karaniwang kahulugan, naiintindihan namin kung ano ito: gusto naming ipagpatuloy ang laro mula sa kung saan kami tumigil noong nakaraang pagkakataon. Upang gawin ito, lumikha kami ng isang uri ng "checkpoint", na pagkatapos ay gagamitin namin upang i-load ang laro. Ngunit ano ang ibig sabihin nito, hindi sa pang-araw-araw na kahulugan, ngunit sa kahulugan ng "programmer"? Ang sagot ay simple: nai-save namin ang estado ng aming programa. Sabihin nating naglalaro ka ng isang laro ng diskarte para sa Spain. Ang iyong laro ay may estado: kung sino ang nagmamay-ari kung anong mga teritoryo, sino ang may kung gaano karaming mga mapagkukunan, sino ang kaalyado kung kanino, at kung sino, sa kabaligtaran, ay nasa digmaan, at iba pa. Ang impormasyong ito, ang estado ng aming programa, ay dapat na mai-save sa anumang paraan upang maibalik sa ibang pagkakataon ang data at ipagpatuloy ang laro. Ito ay tiyak na ginagamit para sa mga mekanismo ng serialization at deserialization . Ang serialization ay ang proseso ng pag-iimbak ng estado ng isang bagay sa isang sequence ng mga byte. Ang deserialization ay ang proseso ng muling pagtatayo ng isang bagay mula sa mga byte na ito. Anumang Java object ay na-convert sa isang sequence ng mga byte. Para saan ito? Sinabi namin nang higit sa isang beses na ang mga programa ay hindi umiiral sa kanilang sarili. Kadalasan ay nakikipag-ugnayan sila sa isa't isa, nagpapalitan ng data, atbp. At ang format ng byte ay maginhawa at mahusay para dito. Maaari naming, halimbawa, SavedGamegawing isang sequence ng byte ang aming class object (isang naka-save na laro), ilipat ang mga byte na iyon sa network patungo sa isa pang computer, at sa pangalawang computer ay gawing Java object ang mga byte na iyon! Ang hirap pakinggan noh? Tila, ang pag-aayos ng prosesong ito ay hindi magiging madali :/ Sa kabutihang palad, hindi! :) Sa Java, ang Serializable interface ay responsable para sa mga proseso ng serialization . Napakasimple ng interface na ito: hindi mo kailangang magpatupad ng isang paraan para magamit ito! Ganito ang magiging hitsura ng aming klase ng save game:
import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoriesInfo;
   private String[] resourcesInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoriesInfo, String[] resourcesInfo, String[] diplomacyInfo){
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(String[] territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public String[] getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(String[] resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

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

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

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + Arrays.toString(territoriesInfo) +
               ", resourcesInfo=" + Arrays.toString(resourcesInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
Tatlong data set ang may pananagutan para sa impormasyon tungkol sa mga teritoryo, ekonomiya at diplomasya, at ang Serializable na interface ay nagsasabi sa Java machine: " ang lahat ay ok, kung mayroon man, ang mga bagay ng klase na ito ay maaaring i-serialize ." Ang isang interface na walang anumang mga pamamaraan ay mukhang kakaiba :/ Bakit ito kailangan? Ang sagot sa tanong na ito ay nasa itaas: para lamang magbigay ng kinakailangang impormasyon sa Java machine. Sa isa sa mga nakaraang lektura, 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 kailangang ipatupad. Kaya, ang Serializable ay isa sa mga ganitong interface. Isa pang mahalagang punto: ang variable private static final long serialVersionUIDna tinukoy namin sa klase. Bakit kailangan ito? Ang field na ito ay naglalaman ng natatanging identifier ng bersyon ng serialized na klase . Ang anumang klase na nagpapatupad ng Serializable na interface ay may identifier ng bersyon. Kinakalkula ito batay sa mga nilalaman ng klase - mga patlang, pagkakasunud-sunod ng deklarasyon, mga pamamaraan. At kung babaguhin namin ang uri ng field at/o bilang ng mga field sa aming klase, agad na magbabago ang identifier ng bersyon. Ang serialVersionUID ay isinusulat din kapag ang klase ay serialized. Kapag sinubukan naming i-deserialize, iyon ay, ibalik ang isang bagay mula sa isang hanay ng mga byte, ang halaga serialVersionUIDay inihambing sa halaga serialVersionUIDng klase sa aming programa. Kung hindi magkatugma ang mga value, itatapon ang isang java.io.InvalidClassException. Makakakita tayo ng isang halimbawa nito sa ibaba. Upang maiwasan ang mga ganitong sitwasyon, manu-mano lang naming itinakda ang version ID na ito para sa aming klase. Sa aming kaso, ito ay magiging katumbas lamang ng 1 (maaari mong palitan ang anumang iba pang numero na gusto mo). Well, oras na para subukang i-serialize ang aming object SavedGameat 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[] resourcesInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a position of neutrality"};

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

       //create 2 threads 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 game to file
       objectOutputStream.writeObject(savedGame);

       // close the stream and release resources
       objectOutputStream.close();
   }
}
Gaya ng nakikita mo, nakagawa kami ng 2 thread - FileOutputStreamat ObjectOutputStream. Ang una sa kanila ay maaaring magsulat ng data sa isang file, at ang pangalawa ay maaaring mag-convert ng mga bagay sa mga byte. Nakakita ka na ng mga katulad na "nested" na mga konstruksyon, halimbawa, new BufferedReader(new InputStreamReader(...))sa mga nakaraang lektura, kaya hindi ka dapat matakot :) Sa pamamagitan ng paglikha ng tulad ng isang "kadena" ng dalawang mga thread, ginagawa namin ang parehong mga gawain: ginagawa namin ang bagay SavedGamesa isang hanay ng mga byte at i-save ito sa isang file gamit ang method writeObject(). At, sa pamamagitan ng paraan, hindi namin nasuri kung ano ang nakuha namin! Oras na para tingnan ang file! *Tandaan: ang file ay hindi kailangang gawin nang maaga. Kung walang file na may ganoong pangalan, awtomatiko itong malilikha* At narito ang mga nilalaman nito! ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранцвоюет SЃСР РА їР° PULONG SA PULONG TUNGKOL SA PILIPINAS таt Р J R 80 Русский Русский t ! РЅС †РёР№t %РЈ Р РѕСЃСЃРёРё 10 РїСЂРѕРІРёРЅС †РёР№t &РЈ ФранцРеРё 8 проввинций Oops :( Mukhang hindi gumana ang program namin :( Sa totoo lang, gumana ito. Naaalala mo na kami ay inilipat sa isang set nang eksakto hindi lang isang bagay o text? Well, ganito ang hitsura ng set na ito :) Ito ang aming na-save na laro! Kung gusto naming ibalik ang aming orihinal na bagay, iyon ay, i-load at ipagpatuloy ang laro mula sa kung saan kami tumigil, kailangan namin ang reverse process , deserialization ... Ito ang magiging hitsura nito sa aming kaso:
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{territoriesInfo=[May 6 na probinsya ang Spain, may 10 provinces ang Russia, may 8 provinces ang France], resourcesInfo=[May 100 gold ang Spain, may 80 gold ang Russia, may 90 gold ang France], diplomacyInfo=[Nakipagdigma ang France sa Russia, Sinakop ng Espanya ang posisyon ng neutralidad]} Mahusay! 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, ngunit alisin SavedGameang pagkakakilanlan ng bersyon mula sa aming klase. Hindi namin muling isusulat ang pareho sa aming mga klase, ang code sa kanila ay magiging pareho, SavedGameaalisin lang namin private static final long serialVersionUID. Narito ang aming object pagkatapos ng serialization: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТВзй{G xp t pФравРССРї ‚ СЃ Россией, Р˜СЃРїР°РЅРЏР·Р°РЅСЏР»Р° RїРусРусский ~~~~~~~~~~~~~~~ "РЈ Р˜СЃРї ании 100 Р · Salita ng tubig 80 sa ritwal ! ёРё 6 R 10 ѕРІРІРёРЅС†РёРЅА At kapag sinusubukang i-deserialize ito, ito ang nangyari: InvalidClassException: local class incompatible : stream classdesc serialVersionUID = - 196410440475012755, local class serialVersionUID = -6675950253085108747 Ito ang parehong pagbubukod na binanggit sa itaas. Mababasa mo ang higit pa tungkol dito sa artikulo ng isa sa aming mga mag-aaral. Siya nga pala, napalampas namin ang isang mahalagang punto. Malinaw na ang mga string at primitives ay madaling serialized: Java ay tiyak na may ilang- pagkatapos ay may mga built-in na mekanismo para dito. Ngunit paano kung ang aming serializable-class ay may mga patlang na ipinahayag hindi bilang mga primitive, ngunit bilang mga sanggunian sa iba pang mga bagay? Halimbawa , gumawa tayo ng hiwalay na mga klase TerritoriesInfoupang gumana sa ating klase . ResourcesInfoDiplomacyInfoSavedGame
public class TerritoriesInfo {

   private String info;

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

   public String getInfo() {
       return info;
   }

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

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

public class ResourcesInfo {

   private String info;

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

   public String getInfo() {
       return info;
   }

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

   @Override
   public String toString() {
       return "ResourcesInfo{" +
               "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 + '\'' +
               '}';
   }
}
Ngunit ngayon ay mayroon tayong tanong: dapat bang maging Serializable ang lahat ng klaseng ito kung gusto nating i-serialize ang binagong klase SavedGame?
import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoriesInfo getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(TerritoriesInfo territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public ResourcesInfo getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(ResourcesInfo resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

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

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + territoriesInfo +
               ", resourcesInfo=" + resourcesInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Well, suriin natin ito sa pagsasanay! Iwanan natin ang lahat sa ngayon at subukang i-serialize ang object 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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, 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 Nabigo! Actually, eto na ang sagot sa tanong namin. Kapag nagse-serialize ka ng isang object, lahat ng object na tinutukoy nito sa mga instance variable nito ay serialized. At kung ang mga bagay na iyon ay tumutukoy din sa mga pangatlong bagay, ang mga ito ay serialized din. At iba pa ang ad infinitum. Ang lahat ng mga klase sa chain na ito ay dapat na Serializable, kung hindi, hindi sila magiging serializable at isang exception ang itatapon. Ito, sa pamamagitan ng paraan, ay maaaring lumikha ng mga problema sa hinaharap. Ano ang dapat nating gawin, halimbawa, kung hindi natin kailangan ng bahagi ng klase sa panahon ng serialization? O, halimbawa, TerritoryInfominana namin ang isang klase sa aming programa bilang bahagi ng ilang library. Gayunpaman, hindi ito Serializable, at, nang naaayon, hindi namin ito mababago. Lumalabas na hindi kami makakapagdagdag ng field TerritoryInfosa aming klase , dahil magiging non-serializable ang buong klase! Problema:/ Ang ganitong uri ng mga problema ay nalulutas sa Java gamit ang keyword . Kung idinagdag mo ang keyword na ito sa isang field ng klase, hindi ise-serialize ang halaga ng field na ito. Subukan nating gumawa ng isa sa mga field ng ating class , pagkatapos nito ay magse-serialize tayo at magre-restore ng isang object. SavedGameSavedGameSerialization at Deserialization sa Java - 2transientSavedGame transient
import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, 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{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='Ang Spain ay may 100 ginto, ang Russia ay may 80 ginto, ang France ay may 90 ginto'}, diplomacyInfo=DiplomacyInfo{info='France ay nakikipagdigma sa Russia, Kinuha ng Spain ang isang posisyong neutralidad'}} Kasabay nito, nakatanggap kami ng sagot sa tanong kung anong halaga ang itatalaga transientsa -field. Ito ay itinalaga ng isang default na halaga. Sa kaso ng mga bagay ito ay null. Sa iyong paglilibang, maaari mong basahin ang mahusay na artikulong ito tungkol sa serialization . Pinag-uusapan din nito ang interface Externalizable, na pag-uusapan natin sa susunod na lecture. Bilang karagdagan, mayroong isang 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