CodeGym/Blog Java/Ngẫu nhiên/Sự khác biệt giữa tuần tự hóa và giải tuần tự hóa trong J...

Sự khác biệt giữa tuần tự hóa và giải tuần tự hóa trong Java là gì?

Xuất bản trong nhóm
CHÀO! Trong bài học hôm nay, chúng ta nói về serialization và deserialization trong Java. Chúng ta sẽ bắt đầu với một ví dụ đơn giản. Giả sử bạn đã tạo một trò chơi trên máy tính. Nếu bạn lớn lên vào những năm 90 và nhớ về máy chơi game thời đó, bạn có thể biết rằng chúng thiếu một thứ mà chúng ta coi là hiển nhiên ngày nay — khả năng lưu và tải trò chơi :) Nếu không, hãy tưởng tượng điều đó! Sự khác biệt giữa tuần tự hóa và giải tuần tự hóa trong Java là gì?  - 1 Tôi sợ rằng ngày nay một trò chơi không có những khả năng này sẽ bị tiêu diệt! Dù sao thì việc "lưu" và "tải" một trò chơi có nghĩa là gì? Chà, chúng tôi hiểu nghĩa thông thường: chúng tôi muốn tiếp tục trò chơi từ nơi chúng tôi đã dừng lại. Để làm điều này, chúng tôi tạo một loại "điểm kiểm tra", sau đó chúng tôi sử dụng điểm này để tải trò chơi. Nhưng điều này có ý nghĩa gì đối với một lập trình viên hơn là một game thủ bình thường? Câu trả lời rất đơn giản: chúng tôi. Giả sử bạn đang chơi với tư cách là người Tây Ban Nha trong Strategium. Trò chơi của bạn có một trạng thái: ai sở hữu lãnh thổ nào, ai có bao nhiêu tài nguyên, ai liên minh với ai, ai gây chiến với ai, v.v. Bằng cách nào đó, chúng ta phải lưu thông tin này, trạng thái chương trình của chúng ta, để khôi phục nó trong tương lai và tiếp tục trò chơi. Vì đây chính xác là mục đích của việc tuần tự hóagiải tuần tự hóa . Tuần tự hóa là quá trình lưu trữ trạng thái của một đối tượng trong một chuỗi byte. khử lưu huỳnhlà quá trình khôi phục một đối tượng từ các byte này. Bất kỳ đối tượng Java nào cũng có thể được chuyển đổi thành chuỗi byte. Tại sao chúng ta cần điều đó? Chúng tôi đã nhiều lần nói rằng các chương trình không tự tồn tại. Thông thường, chúng tương tác với các chương trình khác, trao đổi dữ liệu, v.v. Và chuỗi byte là một định dạng thuận tiện và hiệu quả. Ví dụ: chúng ta có thể biến SavedGameđối tượng của mình thành một chuỗi byte, gửi các byte này qua mạng đến một máy tính khác, sau đó trên máy tính thứ hai biến các byte này trở lại thành một đối tượng Java! Nghe có vẻ khó đúng không? Và việc thực hiện quy trình này có vẻ như là một điều khó khăn :/ May mắn thay, điều này không phải như vậy! :) Trong Java,Serializablegiao diện chịu trách nhiệm cho quá trình tuần tự hóa. Giao diện này cực kỳ đơn giản: bạn không cần triển khai một phương thức duy nhất để sử dụng nó! Đây là lớp tiết kiệm trò chơi của chúng tôi trông đơn giản như thế nào:
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) +
               '}';
   }
}
Ba mảng chịu trách nhiệm cung cấp thông tin về lãnh thổ, tài nguyên và ngoại giao. Giao diện Serializable báo cho máy ảo Java: " Mọi thứ đều ổn - nếu cần, các đối tượng của lớp này có thể được tuần tự hóa ". Một giao diện không có giao diện duy nhất trông kỳ lạ :/ Tại sao lại cần thiết? Câu trả lời cho câu hỏi này có thể được nhìn thấy ở trên: nó chỉ dùng để cung cấp thông tin cần thiết cho máy ảo Java. Trong một trong những bài học trước của chúng tôi, chúng tôi đã đề cập ngắn gọn về giao diện đánh dấu . Đây là những giao diện thông tin đặc biệt chỉ đơn giản đánh dấu các lớp của chúng tôi bằng thông tin bổ sung sẽ hữu ích cho máy Java trong tương lai. Họ không có bất kỳ phương pháp nào mà bạn phải thực hiện.Serializablelà một trong những giao diện đó. Một điểm quan trọng khác: Tại sao chúng ta cần private static final long serialVersionUIDbiến mà chúng ta đã định nghĩa trong lớp? Tại sao nó cần thiết? Trường này chứa một mã định danh duy nhất cho phiên bản của lớp được đánh số tự do . Bất kỳ lớp nào thực hiện Serializablegiao diện đều có một versionmã định danh. Nó được tính toán dựa trên nội dung của lớp: các trường của nó, thứ tự chúng được khai báo, các phương thức, v.v. Nếu chúng ta thay đổi loại trường và/hoặc số lượng trường trong lớp của mình, thì định danh phiên bản sẽ thay đổi ngay lập tức . serialVersionUIDcũng được viết khi lớp được tuần tự hóa. Khi chúng tôi cố gắng giải tuần tự hóa, nghĩa là khôi phục một đối tượng từ một tập hợp byte, liên kết serialVersionUIDđược so sánh với giá trị củaserialVersionUIDcho lớp học trong chương trình của chúng tôi. Nếu các giá trị không khớp, thì java.io. UnlimitedClassException sẽ bị ném. Chúng ta sẽ thấy một ví dụ về điều này dưới đây. Để tránh điều này, chúng tôi chỉ cần đặt số nhận dạng phiên bản theo cách thủ công trong lớp của mình. Trong trường hợp của chúng tôi, nó sẽ đơn giản bằng 1 (nhưng bạn có thể thay thế bất kỳ số nào khác mà bạn thích). Chà, đã đến lúc cố gắng tuần tự hóa SavedGameđối tượng của chúng ta và xem điều gì sẽ xảy ra!
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();
   }
}
Như bạn có thể thấy, chúng tôi đã tạo 2 luồng: FileOutputStreamObjectOutputStream. Cái đầu tiên có thể ghi dữ liệu vào một tệp và cái thứ hai chuyển đổi các đối tượng thành byte. Ví dụ, bạn đã thấy các cấu trúc "lồng nhau" tương tự, new BufferedReader(new InputStreamReader(...))trong các bài học trước, vì vậy những cấu trúc đó sẽ không làm bạn sợ :) Bằng cách tạo một "chuỗi" hai luồng như vậy, chúng ta thực hiện cả hai nhiệm vụ: chúng ta chuyển đổi đối tượng SavedGamethành một tập hợp byte và lưu nó vào một tệp bằng writeObject()phương thức. Và, nhân tiện, chúng tôi thậm chí còn không nhìn vào những gì chúng tôi có! Đã đến lúc xem hồ sơ! *Lưu ý: bạn không phải tạo file trước. Nếu một tệp có tên đó không tồn tại, nó sẽ được tạo tự động* Và đây là nội dung của nó!
¬н 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 провинций
Uh-oh :( Có vẻ như chương trình của chúng tôi không hoạt động :( Trên thực tế, nó đã hoạt động. Bạn có nhớ rằng chúng tôi đã gửi một tập hợp các byte, không chỉ là một đối tượng hoặc văn bản, tới tệp không? Chà, đây là cái gì tập hợp các byte trông như thế nào :) Đây là trò chơi đã lưu của chúng ta! Nếu chúng ta muốn khôi phục đối tượng ban đầu của mình, tức là bắt đầu và tiếp tục trò chơi mà chúng ta đã dừng lại, thì chúng ta cần quy trình ngược lại: deserialization . Đây là giao diện trong trò chơi của chúng ta trường hợp:
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);
   }
}
Và đây là kết quả!
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]}
Xuất sắc! Trước tiên, chúng tôi đã cố gắng lưu trạng thái trò chơi của mình vào một tệp, sau đó khôi phục trạng thái đó từ tệp. Bây giờ, hãy thử làm điều tương tự, nhưng không có mã định danh phiên bản cho SavedGamelớp của chúng ta. Chúng tôi sẽ không viết lại cả hai lớp học của chúng tôi. Mã của họ sẽ giữ nguyên, nhưng chúng tôi sẽ xóa private static final long serialVersionUIDkhỏi SavedGamelớp. Đây là đối tượng của chúng tôi sau khi tuần tự hóa:
¬н 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 провинций
Nhưng hãy xem điều gì sẽ xảy ra khi chúng ta cố gắng deserialize nó:
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Đây là ngoại lệ mà chúng tôi đã đề cập ở trên. Nhân tiện, chúng tôi đã bỏ lỡ một điều quan trọng. Điều hợp lý là Chuỗi và nguyên hàm có thể dễ dàng được tuần tự hóa: Java có thể có một số loại cơ chế tích hợp sẵn để thực hiện việc này. Nhưng nếu serializablelớp của chúng ta có các trường không phải là nguyên thủy, mà là tham chiếu đến các đối tượng khác thì sao? Ví dụ: hãy tạo các lớp và lớp riêng biệt TerritoriesInfođể ResourcesInfolàm DiplomacyInfoviệc với SavedGamelớp của chúng ta.
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 + '\'' +
               '}';
   }
}
Và bây giờ một câu hỏi đặt ra: có cần phải có tất cả các lớp này Serializablenếu chúng ta muốn tuần tự hóa SavedGamelớp đã thay đổi của mình không?
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 +
               '}';
   }
}
Vâng, hãy kiểm tra nó! Hãy để nguyên mọi thứ và cố gắng tuần tự hóa một SavedGameđối tượng:
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();
   }
}
Kết quả:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Nó không hoạt động! Về cơ bản, đó là câu trả lời cho câu hỏi của chúng tôi. Khi một đối tượng được tuần tự hóa, tất cả các đối tượng được tham chiếu bởi các biến thể hiện của nó đều được tuần tự hóa. Và nếu các đối tượng đó cũng tham chiếu đến các đối tượng khác, thì chúng cũng được tuần tự hóa. Và cứ như vậy đến vô tận. Tất cả các lớp trong chuỗi này phải làSerializable , nếu không sẽ không thể tuần tự hóa chúng và một ngoại lệ sẽ được đưa ra. Nhân tiện, điều này có thể tạo ra vấn đề trong quá trình thực hiện. Ví dụ, chúng ta nên làm gì nếu chúng ta không cần một phần của lớp khi chúng ta tuần tự hóa? Hoặc, ví dụ, điều gì sẽ xảy ra nếu TerritoryInfolớp học đến với chúng tôi như một phần của thư viện bên thứ ba nào đó. Và giả sử thêm rằng nó không phải như vậy Serializablevà theo đó, chúng ta không thể thay đổi nó. Hóa ra là chúng ta không thể thêm một TerritoryInfotrường vàoSavedGamelớp, bởi vì làm như vậy sẽ làm cho cả SavedGamelớp không thể tuần tự hóa được! Đó là một vấn đề:/ Sự khác biệt giữa tuần tự hóa và giải tuần tự hóa trong Java là gì?  - 2Trong Java, các vấn đề thuộc loại này được giải quyết bằng cách sử dụng transienttừ khóa. Nếu bạn thêm từ khóa này vào một trường trong lớp của mình, thì trường đó sẽ không được đánh số thứ tự. Hãy thử làm cho một trong SavedGamecác trường thể hiện của lớp tạm thời. Sau đó, chúng tôi sẽ tuần tự hóa và khôi phục một đối tượng.
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();


   }
}
Và đây là kết quả:
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'}}
Ngoài ra, chúng tôi đã nhận được câu trả lời cho câu hỏi của mình về giá trị nào được gán cho một transienttrường. Nó được gán giá trị mặc định. Đối với các đối tượng, đây là null. Bạn có thể đọc bài viết xuất sắc này về lập số sê-ri khi bạn có vài phút rảnh rỗi. Nó cũng đề cập đến Externalizablegiao diện mà chúng ta sẽ nói trong bài học tiếp theo. Ngoài ra, cuốn sách "Head-First Java" có một chương về chủ đề này. Chú ý chút đi :)
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào