JavaRush /وبلاگ جاوا /Random-FA /Serialization و Deserialization در جاوا

Serialization و Deserialization در جاوا

در گروه منتشر شد
سلام! در سخنرانی امروز ما در مورد سریال سازی و deserialization در جاوا صحبت خواهیم کرد. بیایید با یک مثال ساده شروع کنیم. فرض کنید شما خالق یک بازی کامپیوتری هستید. اگر در دهه 90 بزرگ شده اید و کنسول های بازی آن زمان را به یاد می آورید، احتمالاً می دانید که آنها امروز فاقد چیزی آشکار هستند - ذخیره و بارگذاری بازی ها :) اگر نه... تصور کنید! می ترسم امروز بازی بدون چنین فرصتی محکوم به شکست باشد! و در واقع، «ذخیره» و «بارگذاری» یک بازی چیست؟ خوب، به معنای معمول، ما می فهمیم که چیست: می خواهیم بازی را از جایی که دفعه قبل متوقف کردیم ادامه دهیم. برای انجام این کار، یک نوع "نقطه بازرسی" ایجاد می کنیم که سپس از آن برای بارگذاری بازی استفاده می کنیم. اما این نه به معنای روزمره، بلکه به معنای «برنامه نویس» به چه معناست؟ پاسخ ساده است: ما وضعیت برنامه خود را ذخیره می کنیم. فرض کنید در حال انجام یک بازی استراتژیک برای اسپانیا هستید. بازی شما حالتی دارد: چه کسی صاحب چه سرزمینی است، چه کسی چند منبع دارد، چه کسی با چه کسی در اتحاد است، و برعکس، چه کسی در حال جنگ است و غیره. این اطلاعات، وضعیت برنامه ما، باید به نحوی ذخیره شود تا بعداً اطلاعات را بازیابی کنیم و بازی را ادامه دهیم. این دقیقاً همان چیزی است که مکانیسم های سریال سازی و سریال سازی برای آن استفاده می شود . سریال سازی فرآیند ذخیره سازی وضعیت یک شی در یک دنباله از بایت ها است. Deserialization فرآیند بازسازی یک شی از این بایت ها است. هر شی جاوا به دنباله ای از بایت ها تبدیل می شود. این برای چیست؟ ما بیش از یک بار گفته ایم که برنامه ها به تنهایی وجود ندارند. اغلب آنها با یکدیگر تعامل دارند، داده ها را مبادله می کنند و غیره. و فرمت بایت برای این کار راحت و کارآمد است. برای مثال می‌توانیم شی کلاس خود SavedGame(یک بازی ذخیره‌شده) را به دنباله‌ای از بایت‌ها تبدیل کنیم، آن بایت‌ها را از طریق شبکه به رایانه دیگری منتقل کنیم و در رایانه دوم آن بایت‌ها را دوباره به یک شی جاوا تبدیل کنیم! شنیدنش سخت است، درست است؟ ظاهراً سازماندهی این روند کار آسانی نخواهد بود :/ خوشبختانه خیر! :) در جاوا، رابط 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 یکی از این رابط ها است. نکته مهم دیگر: متغیری private static final long serialVersionUIDکه در کلاس تعریف کردیم. چرا نیاز است؟ این فیلد حاوی شناسه نسخه منحصر به فرد کلاس سریال شده است . هر کلاسی که رابط Serializable را پیاده سازی می کند دارای شناسه نسخه است. بر اساس محتویات کلاس - فیلدها، ترتیب اعلان، روش ها محاسبه می شود. و اگر نوع فیلد و/یا تعداد فیلدهای کلاس خود را تغییر دهیم، شناسه نسخه فوراً تغییر خواهد کرد. serialVersionUID نیز زمانی نوشته می شود که کلاس سریال شود. هنگامی که ما سعی می کنیم یک شی را از مجموعه ای از بایت ها بازیابی کنیم، مقدار آن serialVersionUIDبا مقدار serialVersionUIDکلاس در برنامه ما مقایسه می شود. اگر مقادیر مطابقت نداشته باشند، یک java.io.InvalidClassException پرتاب می شود. نمونه ای از این را در زیر مشاهده خواهیم کرد. برای جلوگیری از چنین شرایطی، ما به سادگی این شناسه نسخه را به صورت دستی برای کلاس خود تنظیم می کنیم. در مورد ما، به سادگی برابر با 1 خواهد بود (شما می توانید هر عدد دیگری را که دوست دارید جایگزین کنید). خوب، وقت آن است که شیء خود را سریال سازی کنیم 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
       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 position of neutrality"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourcesInfo, diplomacyInfo);

       //create 2 threads 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 game to file
       objectOutputStream.writeObject(savedGame);

       // close the stream and release resources
       objectOutputStream.close();
   }
}
همانطور که می بینید، ما 2 رشته ایجاد کرده ایم - FileOutputStreamو ObjectOutputStream. اولی می تواند داده ها را در یک فایل بنویسد و دومی می تواند اشیاء را به بایت تبدیل کند. شما قبلاً ساختارهای مشابه "تودرتو" را دیده اید، به عنوان مثال، در سخنرانی های قبلی، بنابراین آنها نباید شما را بترسانند :) با ایجاد چنین "زنجیره ای" از دو رشته، ما هر دو کار را انجام می دهیم: ما شی را به مجموعه ای از بایت ها new BufferedReader(new InputStreamReader(...))تبدیل می کنیم. SavedGameو با استفاده از روش آن را در یک فایل ذخیره کنید writeObject(). و، اتفاقا، ما حتی بررسی نکردیم که چه چیزی به دست آورده ایم! وقت آن است که به پرونده نگاه کنید! *توجه: نیازی به ایجاد فایل از قبل نیست. اگر فایلی با آن نام وجود نداشته باشد، به صورت خودکار ایجاد می شود* و اینم محتویات آن! ¬н sr saveDgame [diplomacyInfot [ljava/lang/string ؛ [resourceinfoq ~ [territoriesinfoq ~ xpur [ljava.lang.string ؛ тvзй {g xp t pr¤сђрр ° р №№№№№№№№ ، РЅРёСЏ Р·Р°Ряла позицию нейтралитетаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜СЃРїР°РЅРёРё Р˜СЃРїР°РЅРёРё 6 опании 6Рѕ Ј Р РѕСЃСЃРёРё 10 РїСЂРѕРІРёРЅС †РёР№t &РЈ ФранцРеРё 8 провиинций اوه :( به نظر می رسد برنامه ما کار نمی کند :( در واقع کار کرده است. شما یادتان هست که مجموعه ای از فایل را دقیقاً به یک فایل منتقل کردیم و نه فقط یک شی یا متن؟ خب، این مجموعه شبیه این است :) این بازی ذخیره شده ما است! اگر بخواهیم شی اصلی خود را بازیابی کنیم، یعنی بازی را از جایی که متوقف کردیم بارگذاری کنیم و ادامه دهیم، به فرآیند معکوس، deserialization ... در مورد ما به این صورت خواهد بود:
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=[اسپانیا 6 استان، روسیه 10 استان، فرانسه 8 استان]، resourcesInfo=[اسپانیا 100 طلا، روسیه 80 طلا، فرانسه 90 طلا]، diplomacyInfo=[فرانسه در حال جنگ با روسیه است، اسپانیا موقعیت بی طرفی را اشغال کرده است]} عالی! ما موفق شدیم ابتدا وضعیت بازی خود را در یک فایل ذخیره کنیم و سپس آن را از فایل بازیابی کنیم. حالا بیایید سعی کنیم همین کار را انجام دهیم، اما SavedGameشناسه نسخه را از کلاس خود حذف کنیم. ما هر دو کلاس خود را بازنویسی نمی کنیم، کد موجود در آنها یکسان خواهد بود، ما فقط SavedGameحذف می کنیم private static final long serialVersionUID. در اینجا شیء ما پس از سریال سازی است: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[sourceInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТВзй{G xp t pФраР. ، 0 Р · олотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &олотаt їСЂРѕРІРёРЅС† РёР№t %РЈ Р РѕСЃСЃРёРё 10 провнинцийt &РЈ Фраииї РЅА و هنگام تلاش برای غیر سریالی کردن آن، این اتفاق افتاد: InvalidClassException: کلاس محلی ناسازگار است: جریان classdesc serialVersionUID = - 196410440475012755, local class serialVersionUID = -6675950253085108747 این همان استثنایی است که در بالا ذکر شد شما می توانید در مقاله یکی از دانشجویان ما در این مورد بیشتر بخوانید. اتفاقاً ما یک نکته مهم را از قلم انداختیم. واضح است که رشته ها و ابتدایی ها به راحتی سریال‌سازی می‌شوند: جاوا مطمئناً دارای مکانیزم‌های داخلی برای این کار است. اما چه می‌شود اگر serializableکلاس ما دارای فیلدهایی باشد که نه به صورت اولیه، بلکه به عنوان ارجاع به اشیاء دیگر بیان می‌شوند؟ برای مثال، بیایید کلاس های جداگانه ای 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 + '\'' +
               '}';
   }
}
اما اکنون یک سوال داریم: اگر بخواهیم کلاس تغییر یافته را سریال کنیم، آیا همه این کلاس ها باید 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 +
               '}';
   }
}
خوب، بیایید این را در عمل بررسی کنیم! بیایید فعلاً همه چیز را همانطور که هست رها کنیم و سعی کنیم شی را سریالی کنیم 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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


       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();
   }
}
نتیجه: استثنا در موضوع "اصلی" java.io.NotSerializableException: DiplomacyInfo ناموفق بود! در واقع، این پاسخ به سوال ما است. وقتی یک شی را سریال می کنید، تمام اشیایی که در متغیرهای نمونه اش به آنها ارجاع می دهد سریال می شوند. و اگر آن اشیاء نیز به اشیاء سوم اشاره کنند، آنها نیز سریالی هستند. و غیره تا بی نهایت. تمام کلاس‌های این زنجیره باید Serializable باشند، در غیر این صورت قابل سریال‌سازی نخواهند بود و استثنایی ایجاد می‌شود. اتفاقاً این ممکن است در آینده مشکلاتی ایجاد کند. به عنوان مثال، اگر در حین سریال سازی به بخشی از کلاس نیاز نداشته باشیم، چه کاری باید انجام دهیم؟ یا به عنوان مثال، TerritoryInfoما یک کلاس را در برنامه خود به عنوان بخشی از برخی از کتابخانه ها به ارث برده ایم. با این حال، سریال سازی نیست، و بر این اساس، ما نمی توانیم آن را تغییر دهیم. معلوم می‌شود که نمی‌توانیم یک فیلد TerritoryInfoبه کلاس خود اضافه کنیم، زیرا در این صورت کل کلاس غیرقابل سریال‌سازی می‌شود! مشکل:/ مشکلاتی از این دست در جاوا با استفاده از کلمه کلیدی حل می شوند . اگر این کلمه کلیدی را به یک فیلد کلاس اضافه کنید، مقدار این فیلد سریالی نمی شود. بیایید سعی کنیم یکی از فیلدهای کلاس خود را بسازیم ، پس از آن یک شی را سریال سازی و بازیابی می کنیم. SavedGameSavedGameسریال سازی و سریال زدایی در جاوا - 2transientSavedGame transient
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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


       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،sourceInfo=ResourcesInfo{info='اسپانیا 100 طلا، روسیه 80 طلا، فرانسه 90 طلا'}، diplomacyInfo=DiplomacyInfo{info='فرانسه در حال جنگ با روسیه است، اسپانیا موضع بی طرفی گرفته است'}} در همان زمان، ما پاسخی به این سوال دریافت کردیم که چه مقداری transientبه فیلد اختصاص داده خواهد شد. یک مقدار پیش فرض به آن اختصاص داده شده است. در مورد اشیا این است null. در اوقات فراغت، می توانید این مقاله عالی در مورد سریال سازی را بخوانید . همچنین در مورد رابط صحبت می کند Externalizableکه در سخنرانی بعدی در مورد آن صحبت خواهیم کرد. علاوه بر این، فصلی در این زمینه در کتاب "جاوا سر اول" وجود دارد، به آن توجه کنید :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION