CodeGym /وبلاگ جاوا /Random-FA /سریال سازی و سریال زدایی در جاوا
John Squirrels
مرحله
San Francisco

سریال سازی و سریال زدایی در جاوا

در گروه منتشر شد
سلام! در درس امروز درباره سریال سازی و سریال زدایی در جاوا صحبت خواهیم کرد. با یک مثال ساده شروع می کنیم. تصور کنید که یک توسعه دهنده بازی های رایانه ای هستید. اگر در دهه 90 بزرگ شده‌اید و کنسول‌های بازی آن دوران را به یاد می‌آورید، احتمالاً می‌دانید که آنها فاقد چیزی بودند که ما امروز آن را بدیهی می‌دانیم - توانایی ذخیره و بارگذاری بازی‌ها :) اگر نه، آن را تصور کنید! سریال سازی و سریال زدایی در جاوا - 1می ترسم بازی امروز بدون این توانایی ها محکوم به فنا باشد! به هر حال، «ذخیره» و «بارگذاری» یک بازی به چه معناست؟ خوب، ما معنای روزمره را درک می کنیم: می خواهیم بازی را از جایی که متوقف کردیم ادامه دهیم. برای انجام این کار، یک «نقطه چک» ایجاد می کنیم که بعداً برای بارگذاری بازی از آن استفاده می کنیم. اما این برای یک برنامه نویس به جای یک گیمر معمولی چه معنایی دارد؟ پاسخ ساده است: ما وضعیت برنامه خود را ذخیره می کنیم. فرض کنید در یک بازی استراتژیک با اسپانیا بازی می کنید. بازی شما دارای حالتی است: هر کس چه قلمروهایی دارد، چه تعداد منابع دارد، چه اتحادهایی وجود دارد و با چه کسی، چه کسی در جنگ است و غیره. این اطلاعات، وضعیت برنامه ما، باید به نحوی ذخیره شود تا داده ها بازیابی شوند و بازی ادامه یابد. همانطور که اتفاق می افتد، سریال سازی و سریال زدایی مکانیسم هایی هستند که برای این کار استفاده می شوند. سریال سازی در جاوا فرآیند ذخیره وضعیت یک شی به صورت دنباله ای از بایت ها است. Deserialization در جاوا فرآیند بازیابی یک شی از این بایت ها است. هر شی جاوا را می توان به دنباله بایت تبدیل کرد. چرا ما به این نیاز داریم؟ ما بارها گفته ایم که برنامه ها به خودی خود وجود ندارند. اغلب، آنها با یکدیگر تعامل می کنند، داده ها را مبادله می کنند، و غیره. فرمت بایت برای این کار راحت و کارآمد است. به عنوان مثال، ما می‌توانیم یک شی از کلاس SavedGame خود را به دنباله‌ای از بایت‌ها تبدیل کنیم، این بایت‌ها را از طریق شبکه به رایانه دیگری منتقل کنیم و سپس در رایانه دیگر این بایت‌ها را دوباره به یک شی جاوا تبدیل کنیم! سخت به نظر می رسد، نه؟ به نظر می رسد انجام همه اینها دشوار باشد: / خوشبختانه اینطور نیست! :) در جاوا، رابط Serializable مسئولیت فرآیند سریال سازی را بر عهده دارد. این رابط بسیار ساده است: برای استفاده از آن نیازی به پیاده سازی یک روش نیست! ببینید کلاس ما برای ذخیره بازی ها چقدر ساده است:
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) +
               '}';
   }
}
سه آرایه مسئول اطلاعات مربوط به قلمروها، منابع و دیپلماسی هستند و رابط Serializable به ماشین جاوا می گوید: " اگر اشیاء این کلاس را بتوان سریال کرد " همه چیز خوب است. یک رابط بدون رابط واحد عجیب به نظر می رسد :/ چرا لازم است؟ پاسخ به این سوال در بالا داده شده است: فقط برای ارائه اطلاعات لازم به ماشین جاوا لازم است. در درس گذشته به طور خلاصه به رابط های نشانگر اشاره کردیم. اینها رابط های اطلاعاتی ویژه ای هستند که به سادگی کلاس های ما را با اطلاعات اضافی مشخص می کنند که در آینده برای ماشین جاوا مفید خواهد بود. آنها هیچ روشی ندارند که شما باید پیاده سازی کنید. در اینجا Serializable است - یکی از این رابط. در اینجا یک نکته مهم دیگر وجود دارد: چرا به متغیر خصوصی استاتیک نهایی طولانی 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[] 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();
   }
}
همانطور که می بینید، ما 2 جریان ایجاد کردیم: FileOutputStream و ObjectOutputStream . اولی می داند چگونه داده ها را در فایل بنویسد و دومی اشیاء را به بایت تبدیل می کند. شما قبلاً ساختارهای تودرتو مشابهی را دیده اید، به عنوان مثال، new BufferedReader(new InputStreamReader(...)) در درس های قبلی، بنابراین نباید شما را بترسانند :) با ایجاد این زنجیره از دو جریان، ما هر دو کار را انجام می دهیم: شی SavedGame را به دنباله ای از بایت ها تبدیل می کنیم و با استفاده از متد writeObject() آن را در یک فایل ذخیره می کنیم . و اتفاقاً ما حتی به چیزی که به دست آوردیم نگاه نکردیم! وقت آن است که به پرونده نگاه کنید! *توجه: نیازی به ایجاد فایل از قبل نیست. اگر فایلی با نام مشخص شده وجود نداشته باشد، به طور خودکار ایجاد می شود* و اینها محتویات آن است: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang. String;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°РЅРёСЏ занялип №С‚ралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ ФроР0 »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 РЏСЂРёРё 10 РЏСЂРЄСФин †РёРё 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{territoryInfo=[اسپانیا 6 استان، روسیه 10 استان، فرانسه 8 استان]، resourceInfo=[اسپانیا 100 طلا، روسیه 80 طلا، فرانسه 90 طلا]، diplomacyInfo=[فرانسه در حال جنگ با روسیه است، اسپانیا موضع بی طرفی گرفته است]} عالی! ما موفق شدیم ابتدا وضعیت بازی خود را در یک فایل ذخیره کنیم و سپس آن را از فایل بازیابی کنیم. حالا بیایید همین کار را انجام دهیم، اما شناسه نسخه را از کلاس SavedGame خود حذف می کنیم . ما هر دو کلاس خود را بازنویسی نمی کنیم. کد آنها یکسان خواهد بود. ما فقط سریال طولانی استاتیک خصوصی VersionUID را از کلاس SavedGame حذف می کنیم . این شیء ما پس از سریال سازی است: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФра " Рё 100 Р ·РѕР»РѕС‚Р°t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &олотаt ІРёРЅС 10 : InvalidClassException : کلاس محلی ناسازگار است: جریان classdesc serialVersionUID = - 196410440475012755، کلاس محلی serialVersionUID = -6675950253085108747 به هر حال، ما چیز مهمی را از دست دادیم. بدیهی است که رشته ها و ابتدایی ها به راحتی سریال می شوند: جاوا قطعا مکانیزم داخلی برای این کار دارد. بلکه ارجاع به اشیاء دیگر؟ برای مثال، بیایید کلاس TerritoryInfo ، ResourceInfo و DiplomacyInfo جداگانه ایجاد کنیم تا با کلاس SavedGame خود کار کنیم .
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 + '\'' +
               '}';
   }
}
و اکنون با یک سوال روبرو هستیم: اگر می‌خواهیم کلاس SavedGame خود را سریال کنیم ، آیا باید همه این کلاس‌ها Serializable باشند ؟
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 +
               '}';
   }
}
باشه پس بیایید آن را آزمایش کنیم! در حال حاضر، همه چیز را همانطور که هست رها می کنیم و سعی می کنیم یک شی 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(territoryInfo, resourceInfo, 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 به کلاس SavedGame خود اضافه کنیم ، زیرا در این صورت کل کلاس SavedGame غیرقابل سریال سازی می شود! این یک مشکل است: / سریال سازی و سریال زدایی در جاوا - 2در جاوا، این نوع مشکل با کلمه کلیدی گذرا حل می شود . اگر این کلمه کلیدی را به فیلدی از کلاس خود اضافه کنید، آن قسمت سریالی نمی شود. بیایید سعی کنیم یکی از فیلدهای کلاس SavedGame خود را گذرا کنیم و سپس یک شی واحد را سریال‌سازی و بازیابی می‌کنیم.
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();


   }
}
و نتیجه این است: SavedGame{territoryInfo=null، resourceInfo=ResourceInfo{info='اسپانیا 100 طلا، روسیه 80 طلا، فرانسه 90 طلا'}، diplomacyInfo=DiplomacyInfo{info='فرانسه در حال جنگ با روسیه است، اسپانیا موضع خنثی گرفته است'}} با این حال، ما به این سوال پاسخ دادیم که چه مقداری به یک فیلد گذرا اختصاص داده خواهد شد . به آن مقدار پیش فرض اختصاص داده شده است. برای اشیا، این عدد تهی است . شما می توانید یک فصل عالی در مورد این موضوع را در کتاب 'Head-First Java' بخوانید، به آن توجه کنید :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION