CodeGym /Java blogg /Slumpmässig /Serialisering och deserialisering i Java
John Squirrels
Nivå
San Francisco

Serialisering och deserialisering i Java

Publicerad i gruppen
Hej! I dagens lektion kommer vi att prata om serialisering och deserialisering i Java. Vi börjar med ett enkelt exempel. Föreställ dig att du är en datorspelsutvecklare. Om du växte upp på 90-talet och minns spelkonsolerna från den tiden, 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!Serialisering och deserialisering i Java - 1Jag är rädd att ett spel utan dessa förmågor idag skulle vara dömt! Hur som helst, vad betyder det att "spara" och "ladda" ett spel? Tja, vi förstår den vardagliga innebörden: vi vill fortsätta spelet från den plats där vi slutade. För att göra detta skapar vi en viss "kontrollpunkt" som vi använder senare för att ladda spelet. Men vad betyder det för en programmerare snarare än en casual gamer? Svaret är enkelt: vi sparar tillståndet för vårt program. Låt oss säga att du spelar Spanien i ett strategispel. Ditt spel har tillstånd: vilka territorier alla har, hur många resurser alla har, vilka allianser finns och med vem, vem som är i krig och så vidare. Denna information, vårt programs tillstånd, måste på något sätt sparas för att kunna återställa data och fortsätta spelet. När det händer, Serialisering i Java är processen att spara ett objekts tillstånd som en sekvens av byte. Deserialisering i Java är processen att återställa ett objekt från dessa bytes. Alla Java-objekt kan konverteras till en bytesekvens. Varför behöver vi detta? Vi har upprepade gånger sagt att program inte existerar av sig själva. Oftast interagerar de med varandra, utbyter data etc. Ett byteformat är bekvämt och effektivt för detta. Till exempel kan vi konvertera ett objekt i vårt SavedGameklass till en sekvens av byte, överför dessa byte över nätverket till en annan dator och sedan på den andra datorn konvertera dessa byte tillbaka till ett Java-objekt! Det låter svårt, va? Det verkar som om det skulle vara svårt att få allt att hända: / Lyckligtvis är det inte så! :) I Java är det serialiseringsbara gränssnittet ansvarigt 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! Titta på hur enkel vår klass för att spara spel är:

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) +
               '}';
   }
}
Tre arrayer är ansvariga för information om territorier, resurser och diplomati, och det serialiserbara gränssnittet säger till Java-maskinen: ' allt är bra om objekt av denna klass kan serialiseras' . Ett gränssnitt utan ett enda gränssnitt ser konstigt ut :/ Varför är det nödvändigt? Svaret på den frågan ges ovan: det behövs bara för att tillhandahålla den nödvändiga informationen till Java-maskinen. I en tidigare lektion 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. Här är Serializable - ett sådant gränssnitt. Här är en annan viktig punkt: Varför behöver viprivate static final long serialVersionUID- variabel som vi definierade i klassen? Det här fältet innehåller den unika versionsidentifieraren för den serialiserade klassen. Varje klass som implementerar Serializable- gränssnittet har en versionsidentifierare. Det bestäms utifrån innehållet i klassen — fält och deras deklarationsordning, och metoder och deras deklarationsordning. Och om vi ändrar en fälttyp och/eller antalet fält i vår klass ändras versionsidentifieraren omedelbart. SerialVersionUID skrivs också när klassen serialiseras . När vi försöker deserialisera, dvs återställa ett objekt från en bytesekvens, jämförs värdet på serialVersionUID med värdet på serialVersionUIDav klassen i vårt program. Om värdena inte matchar, kommer en java.io.InvalidClassException att kastas. Vi kommer att se ett exempel på detta nedan. För att undvika sådana situationer ställer vi helt enkelt in versionsidentifieraren för vår klass manuellt. I vårt fall blir det helt enkelt lika med 1 (du kan använda vilket annat nummer du vill). Nåväl, det är dags att försöka serialisera vårt SavedGame- objekt 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[] 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();
   }
}
Som du kan se skapade vi 2 strömmar: FileOutputStream och ObjectOutputStream . Den första vet hur man skriver data till filen, 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 den här kedjan av två strömmar utför vi båda uppgifterna: vi konverterar SavedGame- objektet till en sekvens av byte och sparar det till en fil med metoden writeObject() . Och, förresten, vi tittade inte ens på vad vi fick! Det är dags att titta på filen! *Obs: det är inte nödvändigt att skapa filen i förväg. Om en fil med det angivna namnet inte existerar kommer den att skapas automatiskt* Och här är dess innehåll: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang. String;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°ЅР» Р˜СЃРїР°ЅРёР ·РёС†РёСЋ нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 Р·РѕРЈРѕ! Рё 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Россииииинцийt Р№t &РЈ Франции 8 провинций Oh, oh :( Det verkar som att vårt program inte fungerade : ( Det fungerade faktiskt. Du kommer ihåg att vi skickade en sekvens av byte, och inte bara ett objekt eller text, till filen? Tja, det här är vad denna bytesekvens ser ut som :) Det ä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 ser det ut för oss:

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{territoryInfo=[Spanien har 6 provinser, Ryssland har 10 provinser, Frankrike har 8 provinser], resourceInfo=[Spanien har 100 guld, Ryssland har 80 guld, Frankrike har 90 guld], diplomacyInfo=[Frankrike är i krig med Ryssland, Spanien har intagit en neutral position]} Utmärkt! Vi lyckades först spara tillståndet för vårt spel till en fil och sedan återställa det från filen. Låt oss nu försöka göra samma sak, men vi tar bort versionsidentifieraren från vår SavedGame- klass. Vi kommer inte att skriva om båda våra klasser. Deras kod kommer att vara densamma. Vi tar bara bort privata statiska sista långa serialVersionUID från SavedGame -klassen. Här är vårt objekt efter serialisering: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранСРѕРЕРѕРѕРѕРѕРѕРѕРЕРЕРŠссией, Р˜СЃРїР°РЅРёСЏ заняла позицию нейтралитетняла позицию нейтралитетняла позицию нейтралитетнипиииииииР00 золотР°t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаиипипии їСЂРѕРІРёРЅС†РёР№t %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 РїСЂРѕРБР▄s, vad ska vi försöka ialisera det: InvalidClassException: lokal klass inkompatibel: stream classdesc serialVersionUID = -196410440475012755, lokal klass serialVersionUID = -6675950253085108747 Förresten, vi missade något viktigt. Uppenbarligen serialiseras strängar och primitiver lätt: Java har verkligen en inbyggd mekanism för detta. Men vad händer om vår serialiserbara klass har fält som inte är primitiva, utan snarare referenser till andra objekt? Låt oss till exempel skapa separata klasser TerritoryInfo , ResourceInfo och DiplomacyInfo för att fungera med vår SavedGame- klass.

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 + '\'' +
               '}';
   }
}
Och nu står vi inför en fråga: Måste alla dessa klasser kunna serialiseras om vi vill serialisera vår SavedGame- klass?

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 +
               '}';
   }
}
Okej då! Låt oss testa det! För tillfället lämnar vi allt som det är och försöker serialisera ett SavedGame- objekt:

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();
   }
}
Resultat: Undantag i tråden "huvud" java.io.NotSerializableException: DiplomacyInfo Det fungerade inte! Så här är 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 om och om för alltid. Alla klasser i den här kedjan måste kunna serialiseras , annars blir det omöjligt att serialisera dem och ett undantag kommer att kastas. Detta kan förresten skapa problem på vägen. Till exempel, vad ska vi göra om vi inte behöver en del av en klass under serialisering? Eller tänk om vi fick vår TerritoryInfo- klass 'genom arv' som en del av ett bibliotek? Och anta vidare att det inte äroch följaktligen kan vi inte ändra det. Det skulle innebära att vi inte kan lägga till ett TerritoryInfo- fält till vår SavedGame- klass, för då skulle hela SavedGame- klassen bli oserialiserbar! Det är ett problem: / Serialisering och deserialisering i Java - 2I Java löses den här typen av problem med nyckelordet transient . 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 fälten i vår SavedGame- klass transient , och sedan kommer vi att serialisera och återställa ett enda objekt.

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


   }
}
Och här är resultatet: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='Spanien har 100 guld, Ryssland har 80 guld, Frankrike har 90 guld'}, diplomacyInfo=DiplomacyInfo{info='Frankrike är i krig med Ryssland, Spanien har intagit en neutral position'}} Som sagt, vi fick svar på frågan om vilket värde som kommer att tilldelas ett transientfält . Den tilldelas standardvärdet. För objekt är detta null . Du kan läsa ett utmärkt kapitel om detta ämne i boken "Head-First Java", var uppmärksam på det :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION