CodeGym /Java-blogg /Tilfeldig /Serialisering og deserialisering i Java
John Squirrels
Nivå
San Francisco

Serialisering og deserialisering i Java

Publisert i gruppen
Hei! I dagens leksjon skal vi snakke om serialisering og deserialisering i Java. Vi starter med et enkelt eksempel. Tenk deg at du er en dataspillutvikler. Hvis du vokste opp på 90-tallet og husker spillkonsollene fra den tiden, vet du sannsynligvis at de manglet noe vi tar for gitt i dag – muligheten til å lagre og laste inn spill :) Hvis ikke, forestill deg det!Serialisering og deserialisering i Java - 1Jeg er redd for at et spill uten disse evnene i dag ville være dømt! Uansett, hva betyr det å "lagre" og "laste" et spill? Vel, vi forstår den hverdagslige betydningen: vi ønsker å fortsette spillet fra stedet der vi slapp. For å gjøre dette oppretter vi et bestemt "sjekkpunkt" som vi bruker senere for å laste spillet. Men hva betyr det for en programmerer i stedet for en casual gamer? Svaret er enkelt: vi lagrer tilstanden til programmet vårt. La oss si at du spiller Spania i et strategispill. Spillet ditt har tilstand: hvilke territorier alle har, hvor mange ressurser alle har, hvilke allianser finnes og med hvem, hvem som er i krig, og så videre. Denne informasjonen, programmets tilstand, må på en eller annen måte lagres for å gjenopprette dataene og fortsette spillet. Som det skjer, Serialisering i Java er prosessen med å lagre et objekts tilstand som en sekvens av byte. Deserialisering i Java er prosessen med å gjenopprette et objekt fra disse bytene. Ethvert Java-objekt kan konverteres til en bytesekvens. Hvorfor trenger vi dette? Vi har gjentatte ganger sagt at programmer ikke eksisterer av seg selv. Oftest samhandler de med hverandre, utveksler data osv. Et byteformat er praktisk og effektivt for dette. For eksempel kan vi konvertere et objekt i vårt SavedGameklasse til en sekvens av byte, overføre disse bytene over nettverket til en annen datamaskin, og deretter på den andre datamaskinen konvertere disse bytene tilbake til et Java-objekt! Det høres vanskelig ut, ikke sant? Det virker som det ville være vanskelig å få alt dette til: / Heldigvis er det ikke tilfelle! :) I Java er Serializable- grensesnittet ansvarlig for serialiseringsprosessen. Dette grensesnittet er ekstremt enkelt: Du trenger ikke å implementere en enkelt metode for å bruke det! Se på hvor enkel klassen vår for å lagre spill 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 informasjon om territorier, ressurser og diplomati, og Serializable-grensesnittet forteller Java-maskinen: ' alt er greit hvis objekter av denne klassen kan serialiseres '. Et grensesnitt uten et enkelt grensesnitt ser rart ut :/ Hvorfor er det nødvendig? Svaret på det spørsmålet er gitt ovenfor: det er bare nødvendig for å gi den nødvendige informasjonen til Java-maskinen. I en tidligere leksjon nevnte vi kort markørgrensesnitt. Dette er spesielle informasjonsgrensesnitt som ganske enkelt markerer klassene våre med tilleggsinformasjon som vil være nyttig for Java-maskinen i fremtiden. De har ingen metoder du må implementere. Her er Serializable - et slikt grensesnitt. Her er et annet viktig poeng: Hvorfor trenger viprivate static final long serialVersionUID- variabel som vi definerte i klassen? Dette feltet inneholder den unike versjonsidentifikatoren til den serialiserte klassen. Hver klasse som implementerer Serializable- grensesnittet har en versjonsidentifikator. Det bestemmes basert på innholdet i klassen - felt og deres deklarasjonsrekkefølge, og metoder og deres deklarasjonsrekkefølge. Og hvis vi endrer en felttype og/eller antall felt i klassen vår, endres versjonsidentifikatoren umiddelbart. SerialVersionUID skrives også når klassen serialiseres. Når vi prøver å deserialisere, dvs. gjenopprette et objekt fra en bytesekvens, sammenlignes verdien av serialVersionUID med verdien til serialVersionUIDav klassen i programmet vårt. Hvis verdiene ikke samsvarer, vil en java.io.InvalidClassException bli kastet. Vi ser et eksempel på dette nedenfor. For å unngå slike situasjoner setter vi ganske enkelt versjonsidentifikatoren for klassen vår manuelt. I vårt tilfelle vil det ganske enkelt være lik 1 (du kan bruke et hvilket som helst annet tall du vil). Vel, det er på tide å prøve å serialisere SavedGame- objektet vårt og se hva som skjer!

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 laget 2 strømmer: FileOutputStream og ObjectOutputStream . Den første vet hvordan man skriver data til filen, og den andre konverterer objekter til byte. Du har allerede sett lignende nestede konstruksjoner, for eksempel new BufferedReader(new InputStreamReader(...)) , i tidligere leksjoner, så de burde ikke skremme deg :) Ved å lage denne kjeden av to strømmer, utfører vi begge oppgavene: vi konverterer SavedGame- objektet til en sekvens av byte og lagrer det til en fil ved å bruke writeObject()- metoden. Og forresten så vi ikke engang på hva vi fikk! Det er på tide å se på filen! *Merk: det er ikke nødvendig å opprette filen på forhånd. Hvis en fil med det angitte navnet ikke eksisterer, vil den bli opprettet automatisk* Og her er innholdet: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang. String;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°ЅР» РяРїР°ЅР ·РёС†РёСЋ нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золоссии 80 золо! Рё 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Россииииинцийt Р№t &РЈ Франции 8 провинций Oh, oh :( Det ser ut til at programmet vårt ikke fungerte : ( Faktisk, det fungerte. Husker du at vi sendte en sekvens med byte, og ikke bare et objekt eller tekst, til filen? Vel, dette er hva denne bytesekvensen ser ut som :) Det er vårt lagrede spill! Hvis vi ønsker å gjenopprette vårt opprinnelige objekt, dvs. starte og fortsette spillet der vi slapp, så trenger vi den omvendte prosessen: deserialisering. Slik ser det ut for 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);
   }
}
Og her er resultatet! SavedGame{territoryInfo=[Spania har 6 provinser, Russland har 10 provinser, Frankrike har 8 provinser], resourceInfo=[Spania har 100 gull, Russland har 80 gull, Frankrike har 90 gull], diplomacyInfo=[Frankrike er i krig med Russland, Spania har inntatt en nøytral posisjon]} Utmerket! Vi klarte først å lagre tilstanden til spillet vårt i en fil, og deretter gjenopprette det fra filen. La oss nå prøve å gjøre det samme, men vi fjerner versjonsidentifikatoren fra SavedGame- klassen vår. Vi vil ikke skrive om begge klassene våre. Koden deres vil være den samme. Vi vil bare fjerne private static final long serialVersionUID fra SavedGame- klassen. Her er objektet vårt etter serialisering: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФраСРЅСРѕРёСЕРЕРЕРѕРѕРѕРѕРЕРЕРссией, Р · ° ‚Р °t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаипииии їСЂРѕРІРёРЅС†РёР№t %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 РїСЂРѕРІРёРs men hva skal vi prøve ialize it: InvalidClassException: lokal klasse inkompatibel: stream classdesc serialVersionUID = -196410440475012755, lokal klasse serialVersionUID = -6675950253085108747 Vi gikk forresten glipp av noe viktig. Det er klart at strenger og primitiver er lett å serialisere: Java har absolutt en innebygd mekanisme for dette. Men hva om vår serialiserbare klasse har felt som ikke er primitiver, men snarere referanser til andre objekter? La oss for eksempel lage separate TerritoryInfo- , ResourceInfo- og DiplomacyInfo- klasser for å jobbe med SavedGame- klassen vår.

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 nå står vi overfor et spørsmål: Må alle disse klassene være serialiserbare hvis vi ønsker å serialisere SavedGame- klassen vår?

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 +
               '}';
   }
}
Ok da! La oss teste det! Foreløpig lar vi alt være som det er og prøver å 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: Unntak i tråden "hoved" java.io.NotSerializableException: DiplomacyInfo Det fungerte ikke! Så her er svaret på spørsmålet vårt. Når et objekt serialiseres, serialiseres alle objektene det refereres til av forekomstvariablene. Og hvis disse objektene også refererer til andre objekter, blir de også serialisert. Og videre og videre for alltid. Alle klassene i denne kjeden må kunne serialiseres , ellers vil det være umulig å serialisere dem og et unntak vil bli kastet. Dette kan forresten skape problemer nedover veien. Hva bør vi for eksempel gjøre hvis vi ikke trenger en del av en klasse under serialisering? Eller hva om vi fikk TerritoryInfo- klassen vår "gjennom arv" som en del av et bibliotek? Og anta videre at det ikke erog følgelig kan vi ikke endre det. Det ville bety at vi ikke kan legge til et TerritoryInfo- felt i SavedGame- klassen vår , for da ville hele SavedGame- klassen bli userialiserbar! Det er et problem: / Serialisering og deserialisering i Java - 2I Java løses denne typen problemer med det forbigående nøkkelordet. Hvis du legger til dette nøkkelordet i et felt i klassen din, blir ikke det feltet serialisert. La oss prøve å lage et av feltene i SavedGame- klassen vår transient , og så skal vi serialisere og gjenopprette 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='Spania har 100 gull, Russland har 80 gull, Frankrike har 90 gull'}, diplomacyInfo=DiplomacyInfo{info='Frankrike er i krig med Russland, Spania har tatt en nøytral posisjon'}} Når det er sagt, fikk vi svar på spørsmålet om hvilken verdi som vil bli tildelt et forbigående felt. Den er tildelt standardverdien. For objekter er dette null . Du kan lese et utmerket kapittel om dette emnet i boken "Head-First Java", vær oppmerksom på det :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION