CodeGym /Java blog /Tilfældig /Serialisering og deserialisering i Java
John Squirrels
Niveau
San Francisco

Serialisering og deserialisering i Java

Udgivet i gruppen
Hej! I dagens lektion vil vi tale om serialisering og deserialisering i Java. Vi starter med et simpelt eksempel. Forestil dig, at du er en computerspilsudvikler. Hvis du voksede op i 90'erne og husker den tids spilkonsoller, ved du sikkert, at de manglede noget, vi tager for givet i dag - evnen til at gemme og indlæse spil :) Hvis ikke, så forestil dig det!Serialisering og deserialisering i Java - 1Jeg er bange for, at et spil uden disse evner i dag ville være dømt! I hvert fald, hvad betyder det at 'gemme' og 'indlæse' et spil? Nå, vi forstår hverdagens betydning: vi vil fortsætte spillet fra det sted, hvor vi slap. For at gøre dette opretter vi et bestemt 'check point', som vi bruger senere til at indlæse spillet. Men hvad betyder det for en programmør frem for en casual gamer? Svaret er enkelt: vi gemmer vores programs tilstand. Lad os sige, at du spiller Spanien i et strategispil. Dit spil har tilstand: hvilke territorier alle har, hvor mange ressourcer alle har, hvilke alliancer der eksisterer og med hvem, hvem der er i krig og så videre. Disse oplysninger, vores programs tilstand, skal på en eller anden måde gemmes for at gendanne dataene og fortsætte spillet. Som det sker, Serialisering i Java er processen med at gemme et objekts tilstand som en sekvens af bytes. Deserialisering i Java er processen med at gendanne et objekt fra disse bytes. Ethvert Java-objekt kan konverteres til en byte-sekvens. Hvorfor har vi brug for dette? Vi har gentagne gange sagt, at programmer ikke eksisterer i sig selv. Oftest interagerer de med hinanden, udveksler data osv. Et byteformat er praktisk og effektivt til dette. For eksempel kan vi konvertere et objekt i vores SavedGameklasse til en sekvens af bytes, overføre disse bytes over netværket til en anden computer, og derefter på den anden computer konvertere disse bytes tilbage til et Java-objekt! Det lyder svært, hva'? Det ser ud til, at det ville være svært at få det hele til at ske: / Heldigvis er det ikke tilfældet! :) I Java er Serializable- grænsefladen ansvarlig for serialiseringsprocessen. Denne grænseflade er ekstremt enkel: Du behøver ikke at implementere en enkelt metode for at bruge den! Se, hvor enkel vores klasse til at gemme spil er:

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 arrays er ansvarlige for information om territorier, ressourcer og diplomati, og Serializable-grænsefladen fortæller Java-maskinen: ' alt er fint, hvis objekter af denne klasse kan serialiseres '. En grænseflade uden en enkelt grænseflade ser mærkelig ud :/ Hvorfor er det nødvendigt? Svaret på det spørgsmål er givet ovenfor: det er kun nødvendigt for at give den nødvendige information til Java-maskinen. I en tidligere lektion nævnte vi kort markørgrænseflader. Disse er specielle informationsgrænseflader, der blot markerer vores klasser med yderligere information, som vil være nyttig for Java-maskinen i fremtiden. De har ingen metoder, du skal implementere. Her er Serializable - en sådan grænseflade. Her er et andet vigtigt punkt: Hvorfor har vi brug forprivate static final long serialVersionUID variabel, som vi definerede i klassen? Dette felt indeholder den unikke versions-id for den serialiserede klasse. Hver klasse, der implementerer Serializable- grænsefladen, har en versions-id. Det bestemmes ud fra indholdet af klassen — felter og deres erklæringsrækkefølge, og metoder og deres erklæringsrækkefølge. Og hvis vi ændrer en felttype og/eller antallet af felter i vores klasse, ændres versionsidentifikatoren øjeblikkeligt. SerialVersionUID skrives også , når klassen serialiseres. Når vi forsøger at deserialisere, dvs. genskabe et objekt fra en byte-sekvens, sammenlignes værdien af ​​serialVersionUID med værdien af ​​serialVersionUIDaf klassen i vores program. Hvis værdierne ikke stemmer overens, vil en java.io.InvalidClassException blive kastet. Vi vil se et eksempel på dette nedenfor. For at undgå sådanne situationer indstiller vi blot versionsidentifikatoren for vores klasse manuelt. I vores tilfælde vil det simpelthen være lig med 1 (du kan bruge et hvilket som helst andet tal, du kan lide). Nå, det er tid til at prøve at serialisere vores SavedGame- objekt og se, hvad der sker!

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, har vi oprettet 2 streams: FileOutputStream og ObjectOutputStream . Den første ved, hvordan man skriver data til filen, og den anden konverterer objekter til bytes. Du har allerede set lignende indlejrede konstruktioner, for eksempel new BufferedReader(new InputStreamReader(...)) , i tidligere lektioner, så de burde ikke skræmme dig :) Ved at oprette denne kæde af to strømme udfører vi begge opgaver: vi konverterer SavedGame- objektet til en sekvens af bytes og gemmer det i en fil ved hjælp af writeObject()- metoden. Og i øvrigt så vi ikke engang på, hvad vi fik! Det er tid til at se på filen! *Bemærk: det er ikke nødvendigt at oprette filen på forhånd. Hvis en fil med det angivne navn ikke eksisterer, oprettes den automatisk* Og her er dens indhold: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang. streng ·РёС†РёСЋ нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 Р·РѕРЈРѕ! Рё 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Россиииииинцийt Р№t &РЈ Франции 8 провинций Åh, åh :( Det ser ud til, at vores program ikke virkede : ( Faktisk virkede det. Du kan huske, at vi sendte en sekvens af bytes, og ikke blot et objekt eller tekst, til filen? Nå, det er hvad denne byte-sekvens ligner :) Det er vores gemte spil! Hvis vi vil gendanne vores oprindelige objekt, dvs. starte og fortsætte spillet, hvor vi slap, så har vi brug for den omvendte proces: deserialisering. Sådan ser det ud for os:

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);
   }
}
Og her er resultatet! SavedGame{territoryInfo=[Spanien har 6 provinser, Rusland har 10 provinser, Frankrig har 8 provinser], resourceInfo=[Spanien har 100 guld, Rusland har 80 guld, Frankrig har 90 guld], diplomacyInfo=[Frankrig er i krig med Rusland, Spanien har indtaget en neutral holdning]} Fremragende! Vi formåede først at gemme tilstanden af ​​vores spil til en fil og derefter at gendanne den fra filen. Lad os nu prøve at gøre det samme, men vi fjerner versionsidentifikatoren fra vores SavedGame- klasse. Vi vil ikke omskrive begge vores klasser. Deres kode vil være den samme. Vi fjerner bare den private statiske endelige lange serialVersionUID fra SavedGame- klassen. Her er vores objekt efter serialisering: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранСРѕРѕРѕРѕРѕРѕРѕРѕРЕРЕРŠссией, Р˜ѓрїр ° рSykke ‚Р °t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаииииии їСЂРѕРІРёРЅС†РёР№t %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 РїСЂРѕРБР▄s, hvad der skal ske, men hvad der skal ske ialize it: InvalidClassException: lokal klasse inkompatibel: stream classdesc serialVersionUID = -196410440475012755, lokal klasse serialVersionUID = -6675950253085108747 I øvrigt gik vi glip af noget vigtigt. Det er klart, at strenge og primitiver let serialiseres: Java har bestemt en indbygget mekanisme til dette. Men hvad nu hvis vores serialiserbare klasse har felter, der ikke er primitiver, men derimod referencer til andre objekter? Lad os for eksempel oprette separate TerritoryInfo , ResourceInfo og DiplomacyInfo klasse for at arbejde med vores SavedGame klasse.

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 + '\'' +
               '}';
   }
}
Og nu står vi over for et spørgsmål: Skal alle disse klasser kunne serialiseres , hvis vi vil serialisere vores SavedGame- klasse?

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 +
               '}';
   }
}
Okay så! Lad os teste det! Indtil videre vil vi lade alt være som det er og prøve at serialisere et 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: Undtagelse i tråden "hoved" java.io.NotSerializableException: DiplomacyInfo Det virkede ikke! Så her er svaret på vores spørgsmål. Når et objekt serialiseres, serialiseres alle de objekter, der refereres til af dets instansvariable. Og hvis disse objekter også refererer til andre objekter, bliver de også serialiseret. Og ved og ved for evigt. Alle klasser i denne kæde skal kunne serialiseres , ellers vil det være umuligt at serialisere dem, og en undtagelse vil blive kastet. Det kan i øvrigt skabe problemer hen ad vejen. Hvad skal vi for eksempel gøre, hvis vi ikke har brug for en del af en klasse under serialisering? Eller hvad hvis vi fik vores TerritoryInfo- klasse 'gennem arv' som en del af et bibliotek? Og antag yderligere, at det ikke erog derfor kan vi ikke ændre det. Det ville betyde, at vi ikke kan tilføje et TerritoryInfo- felt til vores SavedGame- klasse, for så ville hele SavedGame- klassen blive userialiserbar! Det er et problem: / Serialisering og deserialisering i Java - 2I Java løses denne slags problemer med det forbigående nøgleord. Hvis du føjer dette søgeord til et felt i din klasse, bliver dette felt ikke serialiseret. Lad os prøve at lave et af felterne i vores SavedGame- klasse transient , og så vil vi serialisere og gendanne et enkelt 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();


   }
}
Og her er resultatet: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='Spanien har 100 guld, Rusland har 80 guld, Frankrig har 90 guld'}, diplomacyInfo=DiplomacyInfo{info='Frankrig er i krig med Rusland, Spanien har indtaget en neutral position'}} Når det er sagt, fik vi svar på spørgsmålet om, hvilken værdi der vil blive tildelt et transient felt. Den tildeles standardværdien. For objekter er dette null . Du kan læse et glimrende kapitel om dette emne i bogen 'Head-First Java', vær opmærksom på det :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION