CodeGym/Java blogg/Slumpmässig/Vad är skillnaden mellan serialisering och deserialiserin...
John Squirrels
Nivå
San Francisco

Vad är skillnaden mellan serialisering och deserialisering i Java?

Publicerad i gruppen
Hej! I dagens lektion pratar vi om serialisering och deserialisering i Java. Vi börjar med ett enkelt exempel. Låt oss säga att du har skapat ett datorspel. Om du växte upp på 90-talet och minns den tidens spelkonsoler, vet du förmodligen att de saknade något som vi tar för givet idag — möjligheten att spara och ladda spel :) Om inte, föreställ dig det! Vad är skillnaden mellan serialisering och deserialisering i Java?  - 1 Jag är rädd att idag skulle ett spel utan dessa förmågor vara dömt! Vad betyder det att "spara" och "ladda" ett spel ändå? Tja, vi förstår den vanliga innebörden: vi vill fortsätta spelet från den plats där vi slutade. För att göra detta skapar vi en slags "checkpoint", som vi sedan använder för att ladda spelet. Men vad betyder detta för en programmerare snarare än en casual gamer? Svaret är enkelt: vi. Låt oss säga att du spelar som Spanien i Strategium. Ditt spel har en stat: vem äger vilka territorier, vem har hur många resurser, vem är i en allians med vem, vem är i krig med vem och så vidare. Vi måste på något sätt spara denna information, vårt programs tillstånd, för att återställa den i framtiden och fortsätta spelet. För det är just detta som serialisering och deseralisering är till för. Serialisering är processen att lagra ett objekts tillstånd i en sekvens av byte. Deserialiseringär processen att återställa ett objekt från dessa bytes. Alla Java-objekt kan konverteras till en bytesekvens. Varför skulle vi behöva det? Vi har sagt mer än en gång att program inte existerar på egen hand. Oftast interagerar de med andra program, utbyter data etc. Och en bytesekvens är ett bekvämt och effektivt format. Till exempel kan vi förvandla vårt SavedGameobjekt till en sekvens av bytes, skicka dessa bytes över nätverket till en annan dator och sedan på den andra datorn förvandla dessa bytes tillbaka till ett Java-objekt! Låter svårt, eller hur? Och att implementera denna process verkar vara jobbigt :/ Lyckligtvis är det inte så! :) I Java, denSerializablegränssnittet ansvarar för serialiseringsprocessen. Det här gränssnittet är extremt enkelt: du behöver inte implementera en enda metod för att använda det! Så här enkelt ser vår spelsparande klass ut:
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) +
               '}';
   }
}
De tre arrayerna är ansvariga för information om territorier, resurser och diplomati. Det serialiserbara gränssnittet säger till den virtuella Java-maskinen: " Allt är OK - om det behövs kan objekt av denna klass serialiseras" . Ett gränssnitt utan ett enda gränssnitt ser konstigt ut :/ Varför är det nödvändigt? Svaret på denna fråga kan ses ovan: det tjänar bara till att tillhandahålla nödvändig information till den virtuella Java-maskinen. I en av våra tidigare lektioner nämnde vi kortfattat markörgränssnitt . Dessa är speciella informationsgränssnitt som helt enkelt markerar våra klasser med ytterligare information som kommer att vara användbar för Java-maskinen i framtiden. De har inga metoder som du måste implementera.Serializableär ett av dessa gränssnitt. En annan viktig punkt: Varför behöver vi private static final long serialVersionUIDvariabeln som vi definierade i klassen? Varför behövs det? Det här fältet innehåller en unik identifierare för versionen av den serialiserade klassen . Alla klasser som implementerar Serializablegränssnittet har en versionidentifierare. Den beräknas baserat på innehållet i klassen: dess fält, i vilken ordning de deklareras, metoder etc. Om vi ​​ändrar typen av fält och/eller antalet fält i vår klass, ändras versionsidentifieraren omedelbart . serialVersionUIDskrivs också när klassen serialiseras. När vi försöker deserialisera, det vill säga återställa ett objekt från en uppsättning byte, serialVersionUIDjämförs den associerade med värdet avserialVersionUIDför klassen i vårt program. Om värdena inte matchar, då en java.io. InvalidClassException kommer att kastas. Vi kommer att se ett exempel på detta nedan. För att undvika detta ställer vi helt enkelt in versionsidentifieraren manuellt i vår klass. I vårt fall blir det helt enkelt lika med 1 (men du kan ersätta vilket annat nummer du vill). Tja, det är dags att försöka serialisera vårt SavedGameobjekt och se vad som händer!
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 neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourcesInfo, 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 free resources
       objectOutputStream.close();
   }
}
Som du kan se skapade vi 2 strömmar: FileOutputStreamoch ObjectOutputStream. Den första kan skriva data till en fil, och den andra konverterar objekt till byte. Du har redan sett liknande "kapslade" konstruktioner, till exempel, , new BufferedReader(new InputStreamReader(...))i tidigare lektioner, så de borde inte skrämma dig :) Genom att skapa en sådan "kedja" av två strömmar utför vi båda uppgifterna: vi konverterar objektet SavedGametill en uppsättning av byte och spara den i en fil med writeObject()metoden. Och, förresten, vi tittade inte ens på vad vi fick! Det är dags att titta på filen! *Obs: du behöver inte skapa filen i förväg. Om en fil med det namnet inte finns skapas den automatiskt* Och här är dess innehåll!
¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Uh-oh :( Det verkar som att vårt program inte fungerade :( Det fungerade faktiskt. Du minns att vi skickade en uppsättning byte, inte bara ett objekt eller text, till filen? Tja, det här är vad som uppsättning byte ser ut :) Detta är vårt sparade spel! Om vi ​​vill återställa vårt ursprungliga objekt, dvs starta och fortsätta spelet där vi slutade, behöver vi den omvända processen: deserialisering. Så här kommer det att se ut i vår fall:
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);
   }
}
Och här är resultatet!
SavedGame{territoriesInfo=["Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces], resourcesInfo=[Spain has 100 gold, Russia has 80 gold, France has 90 gold], diplomacyInfo=[France is at war with Russia, Spain has taken a neutral position]}
Excellent! Vi lyckades först spara vårt spels tillstånd till en fil och sedan återställa det från filen. Låt oss nu försöka göra samma sak, men utan versionsidentifieraren för vår SavedGameklass. Vi kommer inte att skriva om båda våra klasser. Deras kod kommer att förbli densamma, men vi tar bort private static final long serialVersionUIDfrån SavedGameklassen. Här är vårt objekt efter serialisering:
¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Men titta på vad som händer när vi försöker deserialisera det:
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Detta är själva undantaget som vi nämnde ovan. Förresten, vi missade något viktigt. Det är vettigt att strängar och primitiver lätt kan serialiseras: Java har förmodligen någon form av inbyggd mekanism för att göra detta. Men vad händer om vår serializableklass har fält som inte är primitiva, utan snarare referenser till andra objekt? Låt oss till exempel skapa separata och klasser för att arbeta med vår klass TerritoriesInfo.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 + '\'' +
               '}';
   }
}
Och nu uppstår en fråga: behöver alla dessa klasser vara det Serializableom vi vill serialisera vår förändrade SavedGameklass?
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 +
               '}';
   }
}
Nåväl, låt oss testa det! Låt oss lämna allt som det är och försöka serialisera ett SavedGameobjekt:
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(territoriesInfo, resourcesInfo, diplomacyInfo);

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

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Resultat:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Det fungerade inte! I grund och botten är det svaret på vår fråga. När ett objekt serialiseras serialiseras alla objekt som refereras till av dess instansvariabler. Och om dessa objekt också refererar till andra objekt, så är de också serialiserade. Och så vidare i det oändliga. Alla klasser i den här kedjan måste varaSerializable , annars blir det omöjligt att serialisera dem och ett undantag kommer att kastas. Detta kan förresten skapa problem på vägen. Vad ska vi göra om vi till exempel inte behöver en del av en klass när vi serialiserar? Eller, till exempel, tänk om TerritoryInfoklassen kom till oss som en del av något tredjepartsbibliotek. Och anta vidare att det inte är det Serializableoch att vi därför inte kan ändra det. Det visar sig att vi inte kan lägga till ett TerritoryInfofält till vårSavedGameklass, för att göra det skulle göra hela SavedGameklassen icke-serialiserbar! Det är ett problem :/ Vad är skillnaden mellan serialisering och deserialisering i Java?  - 2I Java löses problem av det här slaget med hjälp av transientnyckelordet. Om du lägger till det här nyckelordet i ett fält i din klass kommer det fältet inte att serialiseras. Låt oss försöka göra ett av SavedGameklassens instansfält övergående. Sedan kommer vi att serialisera och återställa ett objekt.
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
       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(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();


   }
}
Och här är resultatet:
SavedGame{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='Spain has 100 gold, Russia has 80 gold, France has 90 gold'}, diplomacyInfo=DiplomacyInfo{info='France is at war with Russia, Spain has taken a neutral position'}}
Dessutom fick vi svar på vår fråga om vilket värde som tilldelas ett transientfält. Den tilldelas standardvärdet. För objekt är detta null. Du kan läsa den här utmärkta artikeln om serialisering när du har några minuter över. Det nämner också Externalizablegränssnittet, som vi kommer att prata om i nästa lektion. Dessutom har boken "Head-First Java" ett kapitel om detta ämne. Ge det lite uppmärksamhet :)
Kommentarer
  • Populär
  • Ny
  • Gammal
Du måste vara inloggad för att lämna en kommentar
Den här sidan har inga kommentarer än