CodeGym /Java блог /Случаен /Каква е разликата между сериализация и десериализация в J...
John Squirrels
Ниво
San Francisco

Каква е разликата между сериализация и десериализация в Java?

Публикувано в групата
здрасти В днешния урок говорим за сериализация и десериализация в Java. Ще започнем с прост пример. Да приемем, че сте създали компютърна игра. Ако сте израснали през 90-те години и си спомняте игровите конзоли от онази епоха, вероятно знаете, че им липсваше нещо, което днес приемаме за даденост — възможността да запазвате и зареждате игри :) Ако не, представете си това! Каква е разликата между сериализация и десериализация в Java?  - 1 Страхувам се, че днес игра без тези способности би била обречена! Какво все пак означава "запазване" и "зареждане" на игра? Е, ние разбираме обикновеното meaning: искаме да продължим играта от мястото, където сме я спрели. За да направим това, ние създаваме един вид "контролна точка", която след това използваме за зареждане на играта. Но Howво означава това за един програмист, а не за обикновен геймър? Отговорът е прост: ние. Да приемем, че играете като Испания в Strategium. Вашата игра има състояние: кой Howви територии притежава, кой колко ресурси има, кой с кого е в съюз, кой с кого воюва и т.н. Трябва по няHowъв начин да запазим тази информация, състоянието на нашата програма, за да я възстановим в бъдеще и да продължим играта. Защото точно за това са сериализацията и десерализацията . Сериализацията е процес на съхраняване на състоянието на обект в поредица от byteове. Десериализацияе процесът на възстановяване на обект от тези byteове. Всеки Java обект може да бъде преобразуван в последователност от byteове. Защо ще имаме нужда от това? Неведнъж сме казвали, че програмите не съществуват сами по себе си. Най-често те взаимодействат с други програми, обменят данни и т.н. А последователността от byteове е удобен и ефективен формат. Например, можем да превърнем нашия SavedGameобект в поредица от byteове, да изпратим тези byteове по мрежата до друг компютър и след това на втория компютър да превърнем тези byteове обратно в Java обект! Звучи трудно, нали? И прилагането на този процес изглежда като болка :/ За щастие, това не е така! :) В Java,Serializableинтерфейсът отговаря за процеса на сериализация. Този интерфейс е изключително прост: не е необходимо да прилагате нито един метод, за да го използвате! Ето колко просто изглежда нашият клас за запазване на игри:

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) +
               '}';
   }
}
Трите масива отговарят за информация за територии, ресурси и дипломация. Интерфейсът Serializable казва на виртуалната машина на Java: „ Всичко е наред — ако е необходимо, обектите от този клас могат да бъдат сериализирани “. Интерфейс без нито един интерфейс изглежда странно :/ Защо е необходимо? Отговорът на този въпрос може да се види по-горе: той служи само за предоставяне на необходимата информация на виртуалната машина на Java. В един от предишните ни уроци споменахме накратко интерфейсите на маркерите . Това са специални информационни интерфейси, които просто маркират нашите класове с допълнителна информация, която ще бъде полезна за Java машината в бъдеще. Те нямат ниHowви методи, които трябва да приложите.Serializableе един от тези интерфейси. Друг важен момент: Защо се нуждаем от private static final long serialVersionUIDпроменливата, която сме дефинирали в класа? Защо е необходимо? Това поле съдържа уникален идентификатор за versionта на сериализирания клас . Всеки клас, който имплементира Serializableинтерфейса, има versionидентификатор. Изчислява се въз основа на съдържанието на класа: неговите полета, реда, в който са декларирани, методи и т.н. Ако променим типа на полето и/or броя на полетата в нашия клас, тогава идентификаторът на versionта незабавно се променя . serialVersionUIDсе записва и когато класът е сериализиран. Когато се опитваме да десериализираме, тоест да възстановим обект от набор от byteове, асоциираният serialVersionUIDсе сравнява със стойността наserialVersionUIDза класа в нашата програма. Ако стойностите не съвпадат, тогава java.io. Ще бъде хвърлено изключение InvalidClassException . Ще видим пример за това по-долу. За да избегнем това, просто задаваме ръчно идентификатора на versionта в нашия клас. В нашия случай то просто ще бъде равно на 1 (но можете да замените всяко друго число, което желаете). Е, време е да опитаме да сериализираме нашия SavedGameобект и да видим Howво ще се случи!

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();
   }
}
Както можете да видите, създадохме 2 потока: FileOutputStreamи ObjectOutputStream. Първият може да записва данни във файл, а вторият преобразува обекти в byteове. Вече сте виждали подобни "вложени" конструкции, например, new BufferedReader(new InputStreamReader(...))в предишни уроци, така че те не трябва да ви плашат :) Създавайки такава "верига" от два потока, ние изпълняваме и двете задачи: преобразуваме обекта SavedGameв набор от byteове и го запазете във файл, като използвате writeObject()метода. И, между другото, ние дори не погледнахме Howво имаме! Време е да погледнете file! *Забележка: не е необходимо да създавате file предварително. Ако файл с това име не съществува, той ще бъде създаден автоматично* И ето неговото съдържание!

¬н 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 провинций
О-о :( Изглежда, че програмата ни не е работила :( Всъщност тя е работила. Спомняте ли си, че изпратихме набор от byteове, а не просто обект or текст, към file? Е, това е, което набор от byteове изглежда :) Това е нашата запазена игра! Ако искаме да възстановим нашия оригинален обект, т.е. случай:

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);
   }
}
И ето го резултата!

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]}
Отлично! Успяхме първо да запазим състоянието на нашата игра във файл и след това да го възстановим от file. Сега нека се опитаме да направим същото, но без идентификатора на versionта за нашия SavedGameклас. Няма да пренаписваме и двата си класа. Техният code ще остане същият, но ще го премахнем private static final long serialVersionUIDот SavedGameкласа. Ето нашия обект след сериализация:

¬н 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 провинций
Но вижте Howво се случва, когато се опитаме да го десериализираме:

InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Това е изключение, което споменахме по-горе. Между другото, пропуснахме нещо важно. Има смисъл, че низовете и примитивите могат лесно да бъдат сериализирани: Java вероятно има няHowъв вграден механизъм за това. Но Howво ще стане, ако нашият serializableклас има полета, които не са примитиви, а по-скоро препратки към други обекти? Например, нека създадем отделни TerritoriesInfoи класове за работа с нашия ResourcesInfoклас . DiplomacyInfoSavedGame

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 + '\'' +
               '}';
   }
}
И сега възниква въпрос: трябва ли всички тези класове да бъдат, Serializableако искаме да сериализираме нашия променен 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 +
               '}';
   }
}
Е, нека го тестваме! Нека оставим всичко Howто е и се опитаме да сериализираме 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
       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();
   }
}
Резултат:

Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Не се получи! По принцип това е отговорът на нашия въпрос. Когато даден обект е сериализиран, всички обекти, посочени от неговите променливи на екземпляр, се сериализират. И ако тези обекти също препращат към други обекти, тогава те също са сериализирани. И така до безкрайност. Всички класове в тази верига трябва да бъдатSerializable , в противен случай ще бъде невъзможно да бъдат сериализирани и ще бъде хвърлено изключение. Между другото, това може да създаде проблеми надолу по пътя. Какво трябва да направим, ако например не се нуждаем от част от клас, когато сериализираме? Или, например, Howво ще стане, ако TerritoryInfoкласът дойде при нас като част от библиотека на трета страна. И да предположим още, че не е Serializableи съответно не можем да го променим. Оказва се, че не можем да добавим TerritoryInfoполе към нашетоSavedGameклас, защото това би направило целия SavedGameклас несериализиран! Това е проблем :/ Каква е разликата между сериализация и десериализация в Java?  - 2В Java проблеми от този вид се решават с помощта на transientключовата дума. Ако добавите тази ключова дума към поле от вашия клас, това поле няма да бъде сериализирано. Нека се опитаме да направим едно от SavedGameполетата на класа преходно. След това ще сериализираме и възстановим един обект.

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


   }
}
И ето резултата:

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'}}
Освен това получихме отговор на въпроса си Howва стойност се присвоява на transientполе. Присвоява му се стойността по подразбиране. За обекти това е null. Можете да прочетете тази отлична статия за сериализацията, когато имате няколко свободни minutesи. Споменава се и Externalizableинтерфейсът, за който ще говорим в следващия урок. Освен това книгата "Head-First Java" има глава по тази тема. Обърнете малко внимание :)
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION