CodeGym /مدونة جافا /Random-AR /ما الفرق بين التسلسل وإلغاء التسلسل في Java؟
John Squirrels
مستوى
San Francisco

ما الفرق بين التسلسل وإلغاء التسلسل في Java؟

نشرت في المجموعة
أهلاً! في درس اليوم، سنتحدث عن التسلسل وإلغاء التسلسل في Java. سنبدأ بمثال بسيط. لنفترض أنك قمت بإنشاء لعبة كمبيوتر. إذا نشأت في التسعينيات وتذكرت وحدات التحكم في الألعاب في تلك الحقبة، فمن المحتمل أنك تعلم أنها كانت تفتقر إلى شيء نعتبره أمرًا مفروغًا منه اليوم - وهو القدرة على حفظ الألعاب وتحميلها :) إذا لم يكن الأمر كذلك، تخيل ذلك! ما الفرق بين التسلسل وإلغاء التسلسل في Java؟  - 1 أخشى أن اللعبة التي لا تحتوي على هذه القدرات اليوم ستكون محكوم عليها بالفشل! ماذا يعني "حفظ" و"تحميل" لعبة على أية حال؟ حسنًا، نحن نفهم المعنى العادي: نريد مواصلة اللعبة من حيث توقفنا. للقيام بذلك، نقوم بإنشاء نوع من "نقطة التفتيش"، والتي نستخدمها بعد ذلك لتحميل اللعبة. ولكن ماذا يعني هذا بالنسبة للمبرمج وليس للاعب العادي؟ الجواب بسيط: نحن نحفظ حالة برنامجنا . لنفترض أنك تلعب بشخصية إسبانيا في لعبة Strategium. لعبتك لها حالة: من يملك أي مناطق، ومن لديه عدد الموارد، ومن في تحالف مع من، ومن في حالة حرب مع من، وما إلى ذلك. يجب علينا بطريقة أو بأخرى حفظ هذه المعلومات، وحالة برنامجنا، من أجل استعادتها في المستقبل ومواصلة اللعبة. لأن هذا هو بالضبط الغرض من التسلسل وإلغاء الواقع . التسلسل هو عملية تخزين حالة الكائن في تسلسل من البايتات. إلغاء التسلسل هو عملية استعادة كائن من هذه البايتات. يمكن تحويل أي كائن Java إلى تسلسل بايت. لماذا نحتاج ذلك؟ لقد قلنا أكثر من مرة أن البرامج لا توجد بمفردها. في أغلب الأحيان، يتفاعلون مع البرامج الأخرى، ويتبادلون البيانات، وما إلى ذلك. وتسلسل البايت هو تنسيق مناسب وفعال. على سبيل المثال، يمكننا تحويل كائننا SavedGameإلى سلسلة من البايتات، وإرسال هذه البايتات عبر الشبكة إلى كمبيوتر آخر، ثم على الكمبيوتر الثاني تحويل هذه البايتات مرة أخرى إلى كائن 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) +
               '}';
   }
}
المصفوفات الثلاثة مسؤولة عن المعلومات حول المناطق والموارد والدبلوماسية. تخبر الواجهة القابلة للتسلسل جهاز Java الظاهري: " كل شيء على ما يرام - إذا لزم الأمر، يمكن إجراء تسلسل لكائنات هذه الفئة ". الواجهة التي لا تحتوي على واجهة واحدة تبدو غريبة:/ لماذا هي ضرورية؟ يمكن رؤية إجابة هذا السؤال أعلاه: فهي تعمل فقط على توفير المعلومات الضرورية لجهاز Java الظاهري. في أحد دروسنا السابقة، ذكرنا بإيجاز واجهات العلامات . هذه واجهات معلوماتية خاصة تقوم ببساطة بتمييز فئاتنا بمعلومات إضافية ستكون مفيدة لجهاز Java في المستقبل. ليس لديهم أي أساليب عليك تنفيذها. Serializableهي واحدة من تلك الواجهات. نقطة أخرى مهمة: لماذا نحتاج إلى private static final long serialVersionUIDالمتغير الذي حددناه في الفصل؟ لماذا هو مطلوب؟ يحتوي هذا الحقل على معرف فريد لإصدار الفئة المتسلسلة . أي فئة تنفذ الواجهة Serializableلها versionمعرف. يتم حسابه بناءً على محتويات الفصل: حقوله، ترتيب الإعلان عنها، الأساليب، وما إلى ذلك. إذا قمنا بتغيير نوع الحقل و/أو عدد الحقول في الفصل، فسيتغير معرف الإصدار على الفور . 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 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();
   }
}
كما ترى، قمنا بإنشاء تيارين: FileOutputStreamو ObjectOutputStream. الأول يمكنه كتابة البيانات إلى ملف، والثاني يحول الكائنات إلى بايت. لقد رأيت بالفعل بنيات "متداخلة" مماثلة، على سبيل المثال، new BufferedReader(new InputStreamReader(...))في الدروس السابقة، لذلك لا ينبغي أن يخيفك ذلك :) من خلال إنشاء مثل هذه "السلسلة" من دفقين، نقوم بتنفيذ كلتا المهمتين: نقوم بتحويل الكائن SavedGameإلى مجموعة بايت وحفظه في ملف باستخدام هذه writeObject()الطريقة. وبالمناسبة، لم ننظر حتى إلى ما حصلنا عليه! حان الوقت لإلقاء نظرة على الملف! *ملاحظة: ليس عليك إنشاء الملف مسبقًا. إذا كان الملف بهذا الاسم غير موجود، فسيتم إنشاؤه تلقائيًا* وإليك محتوياته!

¬н 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 провинций
اه أوه :( يبدو أن برنامجنا لم يعمل :( في الواقع، لقد نجح. هل تتذكر أننا أرسلنا مجموعة من البايتات، وليس مجرد كائن أو نص، إلى الملف؟ حسنًا، هذا هو ما تبدو مجموعة البايتات كما يلي:) هذه هي لعبتنا المحفوظة! إذا أردنا استعادة الكائن الأصلي، أي بدء اللعبة ومواصلتها من حيث توقفنا، فإننا نحتاج إلى العملية العكسية: إلغاء التسلسل . وإليك ما ستبدو عليه في لعبتنا قضية:
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]}
ممتاز! تمكنا أولاً من حفظ حالة لعبتنا في ملف، ثم استعادتها من الملف. الآن دعونا نحاول أن نفعل نفس الشيء، ولكن بدون معرف الإصدار لفصلنا SavedGame. لن نعيد كتابة كلا الفصلين. سيظل الرمز الخاص بهم كما هو، ولكننا سنزيله 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 провинций
لكن انظر إلى ما يحدث عندما نحاول إلغاء تسلسله:

InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
وهذا هو الاستثناء الذي ذكرناه أعلاه. بالمناسبة، فاتنا شيئًا مهمًا. من المنطقي أنه يمكن إجراء تسلسل للسلاسل النصية والأوليات بسهولة: ربما تحتوي Java على نوع من الآلية المضمنة للقيام بذلك. ولكن ماذا لو كان serializableفصلنا يحتوي على حقول ليست أولية، ولكنها تشير إلى كائنات أخرى؟ على سبيل المثال، لنقم بإنشاء فصول منفصلة TerritoriesInfoللعمل ResourcesInfoمع DiplomacyInfoفصلنا SavedGame.
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
       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 ، وإلا سيكون من المستحيل إجراء تسلسل لها وسيتم طرح استثناء. بالمناسبة، هذا يمكن أن يخلق مشاكل في المستقبل. ماذا يجب أن نفعل، على سبيل المثال، إذا لم نكن بحاجة إلى جزء من الفصل الدراسي عندما نقوم بالتسلسل؟ أو، على سبيل المثال، ماذا لو 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'}}
بالإضافة إلى ذلك، حصلنا على إجابة لسؤالنا حول القيمة التي يتم تعيينها للحقل transient. يتم تعيينه القيمة الافتراضية. بالنسبة للكائنات، هذا هو null. يمكنك قراءة هذه المقالة الممتازة حول التسلسل عندما يكون لديك بضع دقائق إضافية. ويذكر أيضًا Externalizableالواجهة التي سنتحدث عنها في الدرس التالي. بالإضافة إلى ذلك، يحتوي كتاب "Head-First Java" على فصل حول هذا الموضوع. اعطها بعض الاهتمام :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION