JavaRush /Blog Java /Random-VI /Tuần tự hóa và giải tuần tự hóa trong Java

Tuần tự hóa và giải tuần tự hóa trong Java

Xuất bản trong nhóm
Xin chào! Trong bài giảng hôm nay chúng ta sẽ nói về tuần tự hóa và giải tuần tự hóa trong Java. Hãy bắt đầu với một ví dụ đơn giản. Giả sử bạn là người tạo ra một trò chơi 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 ngày nay chúng thiếu một thứ hiển nhiên - lưu và tải trò chơi :) Nếu không... hãy tưởng tượng! Tôi e rằng hôm nay một trò chơi không có cơ hội như vậy sẽ thất bại! Và thực ra, “lưu” và “tải” trò chơi là gì? Chà, theo nghĩa thông thường, chúng tôi hiểu nó là gì: 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ần trước. Để làm điều này, chúng tôi tạo ra 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 là gì, không phải theo nghĩa thông thường mà theo nghĩa “lập trình viên”? Câu trả lời rất đơn giản: chúng ta lưu trạng thái của chương trình. Giả sử bạn đang chơi một trò chơi chiến lược cho Tây Ban Nha. 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 và ngược lại, ai đang có chiến tranh, v.v. Thông tin này, trạng thái chương trình của chúng tôi, phải được lưu bằng cách nào đó để sau này khôi phục dữ liệu và tiếp tục trò chơi. Đây chính xác là mục đích mà các cơ chế tuần tự hóagiải tuần tự hóa được sử dụng . Tuần tự hóa là quá trình lưu trữ trạng thái của một đối tượng thành một chuỗi byte. Deserialization là quá trình xây dựng lại một đối tượng từ các byte này. Bất kỳ đối tượng Java nào cũng được chuyển đổi thành một chuỗi byte. Nó dùng để làm gì? Chúng tôi đã nói nhiều lần rằng các chương trình không tự tồn tại. Thông thường họ tương tác với nhau, trao đổi dữ liệu, v.v. Và định dạng byte thuận tiện và hiệu quả cho việc này. Ví dụ: chúng ta có thể biến đối tượng lớp của mình SavedGame(một trò chơi đã lưu) thành một chuỗi byte, chuyển các byte đó qua mạng sang một máy tính khác và trên máy tính thứ hai biến các byte đó trở lại thành đối tượng Java! Thật khó nghe phải không? Có vẻ như việc tổ chức quá trình này sẽ không hề dễ dàng :/ May mắn thay là không! :) Trong Java, giao diện Serializable chịu trách nhiệm về các 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 thực hiện một phương pháp nào để sử dụng nó! Lớp save game của chúng ta sẽ trông như thế này:
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 bộ dữ liệu chịu trách nhiệm về thông tin về lãnh thổ, kinh tế và ngoại giao, đồng thời giao diện Serializable cho máy Java biết: “ mọi thứ đều ổn, nếu có, 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ó bất kỳ phương thức nào 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 nằm ở trên: chỉ cung cấp thông tin cần thiết cho máy Java. Trong một trong những bài giảng trước, chúng tôi đã đề cập ngắn gọn về các giao diện điểm đánh dấu. Đây là những giao diện thông tin đặc biệt giúp đánh dấu các lớp của chúng ta bằng nhữ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 cần phải thực hiện. Vì vậy, Serializable là một trong những giao diện như vậy. Một điểm quan trọng khác: biến private static final long serialVersionUIDchúng ta đã xác định trong lớp. Tại sao nó lại cần thiết? Trường này chứa mã định danh phiên bản duy nhất của lớp được xê-ri hóa . Bất kỳ lớp nào triển khai giao diện Serializable đều có mã nhận dạng phiên bản. Nó được tính toán dựa trên nội dung của lớp - các trường, thứ tự khai báo, phương thức. 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, mã nhận dạng phiên bản sẽ thay đổi ngay lập tức. serialVersionUID cũng được viết khi lớp được tuần tự hóa. Khi chúng ta cố gắng giải tuần tự hóa, tức là khôi phục một đối tượng từ một tập hợp byte, giá trị này serialVersionUIDsẽ được so sánh với giá trị serialVersionUIDcủa lớp trong chương trình của chúng ta. Nếu các giá trị không khớp, một ngoại lệ java.io.InvalidClassException sẽ được ném ra. Chúng ta sẽ thấy một ví dụ về điều này dưới đây. Để tránh những tình huống như vậy, chúng tôi chỉ cần đặt ID phiên bản này cho lớp của mình theo cách thủ công. Trong trường hợp của chúng tôi, nó sẽ đơn giản bằng 1 (bạn có thể thay thế bất kỳ số nào khác mà bạn thích). Chà, đã đến lúc thử tuần tự hóa đối tượng của chúng ta SavedGamevà 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 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();
   }
}
Như bạn có thể thấy, chúng tôi đã tạo 2 luồng - FileOutputStreamObjectOutputStream. Cái đầu tiên trong số chúng có thể ghi dữ liệu vào một tệp và cái thứ hai có thể 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 giảng trước, vì vậy chúng sẽ không làm bạn sợ :) Bằng cách tạo một “chuỗi” gồm hai luồng như vậy, chúng ta thực hiện cả hai nhiệm vụ: chúng ta biến đối tượng SavedGamethành một tập hợp byte và lưu nó vào một tệp bằng phương thức writeObject(). Và nhân tiện, chúng tôi thậm chí còn không kiểm tra những gì chúng tôi có! Đã đến lúc xem tập tin! *Lưu ý: không cần tạo trước file. Nếu 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 [ ngoại giaoInfot [Ljava/lang/String;[ ResourcesInfoq ~ [ lãnh thổInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранцвоюет SЃ R оссией , Р˜С ЃРїР° РЅРёСЏ Р·Р°Ряла позицию нейтралитетаuq ~ t "РЈ Р˜ спании 100 золотаt Р J R РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜С ЃРїР°РЅРёРё 6 РїСЂРѕРІРёРЅС †РёР№t %РЈ Р РѕСЃСЃРёРё 10 РїСЂРѕРІРёРЅС †РёР№t &РЈ ФранцРеРё 8 проввинций Rất tiếc :( 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 nhớ rằng chúng tôi đã chuyển chính xác một tập hợp byte vào tệp và không chỉ là một đối tượng hay văn bản? Chà, bộ này trông như thế này :) Đây là trò chơi đã lưu của chúng tôi! Nếu chúng tôi muốn khôi phục đối tượng ban đầu của mình, tức là tải và tiếp tục trò chơi từ nơi chúng tôi đã dừng lại, chúng tôi cần quá trình đảo ngược, khử lưu huỳnh ... Đây là giao diện trong trường hợp của chúng tôi:
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ả! SavingGame{territoriesInfo=[Tây Ban Nha có 6 tỉnh, Nga có 10 tỉnh, Pháp có 8 tỉnh], ResourcesInfo=[Tây Ban Nha có 100 vàng, Nga có 80 vàng, Pháp có 90 vàng], ngoại giaoInfo=[Pháp đang có chiến tranh với Nga, Tây Ban Nha đã chiếm vị trí trung lập]} Tuyệt vời! 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 nó từ tệp. Bây giờ hãy thử làm tương tự nhưng xóa SavedGamemã nhận dạng phiên bản khỏi lớp của chúng ta. Chúng tôi sẽ không viết lại cả hai lớp của mình, mã trong chúng sẽ giống nhau, chúng tôi sẽ chỉ SavedGamexóa private static final long serialVersionUID. Đây là đối tượng của chúng tôi sau khi tuần tự hóa: ин sr SavedGameі€MіuОm‰ [ ngoại giaoInfot [Ljava/lang/String;[ ResourcesInfoq ~ [ lãnh thổInfoq ~ xpur [Ljava.lang.String;ТВзй{G xp t pФранция РІРѕСЋРµС ‚ СЃ Россией, Р˜СЃРїР°РЅРЏР·Р°РЅСЏР»Р° RїРѕР·РёС†РёСЋ РЅРµР№С ‚ралитетаuq ~ t "РЈ Р˜СЃРї ании 100 Р · олотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 Р·Рѕ лотаuq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 R їСЂРѕРІРёРЅС† РёР№t %РЈ Р РѕСЃСЃРёРё 10 провнинцийt &РЈ Р ¤СЂР°РЅС†РёРё 8 проввинцинА Và khi cố gắng giải tuần tự hóa nó, đây là điều đã xảy ra: InvalidClassException: local class insimilar : streaming classdesc serialVersionUID = - 196410440475012755, lớp cục bộ serialVersionUID = -6675950253085108747 Đây là ngoại lệ tương tự đã được đề cập ở trên. Bạn có thể đọc thêm về điều này trong bài viết của một trong những sinh viên của chúng tôi. Nhân tiện, chúng tôi đã bỏ lỡ một điểm quan trọng. Rõ ràng là chuỗi và nguyên thủy dễ dàng được tuần tự hóa: Java chắc chắn có một số cơ chế tích hợp sẵn cho việc này. Nhưng điều gì sẽ xảy ra nếu serializablelớp - của chúng ta có các trường được biểu thị không phải dưới dạng nguyên thủy mà dưới dạng tham chiếu đến các đối tượng khác? Ví dụ: hãy tạo các lớp riêng biệt TerritoriesInfođể làm việc với lớp của chúng ta . 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 + '\'' +
               '}';
   }
}
Nhưng bây giờ chúng ta có một câu hỏi: tất cả các lớp này có nên Serializable nếu chúng ta muốn serialize lớp đã thay đổi không 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 +
               '}';
   }
}
Vâng, hãy kiểm tra điều này trong thực tế! Bây giờ hãy để mọi thứ như hiện tại và cố gắng tuần tự hóa đối tượng 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();
   }
}
Kết quả: Ngoại lệ trong chuỗi "chính" java.io.NotSerializableException: DiplomacyInfo không thành công! Trên thực tế, đây là câu trả lời cho câu hỏi của chúng tôi. Khi bạn tuần tự hóa một đối tượng, tất cả các đối tượng mà nó tham chiếu trong các biến thể hiện của nó đều được tuần tự hóa. Và nếu những đối tượng đó cũng tham chiếu đến đối tượng thứ ba thì chúng cũng được tuần tự hóa. Và cứ thế đến vô tận. Tất cả các lớp trong chuỗi này phải có khả năng tuần tự hóa, nếu không chúng sẽ không thể tuần tự hóa được 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 tương lai. 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 trong quá trình tuần tự hóa? Hoặc, ví dụ, TerritoryInfochúng ta kế thừa một lớp trong chương trình của mình như một phần của thư viện nào đó. Tuy nhiên, nó không thể tuần tự hóa được và do đó, chúng tôi không thể thay đổi nó. Hóa ra là chúng ta không thể thêm một trường TerritoryInfovào lớp của mình , vì khi đó toàn bộ lớp sẽ trở thành không thể tuần tự hóa được! Vấn đề:/ Các vấn đề thuộc loại này được giải quyết trong Java bằng từ khóa . Nếu bạn thêm từ khóa này vào trường lớp, giá trị của trường này sẽ không được tuần tự hóa. Hãy thử tạo một trong các trường của lớp chúng ta , sau đó chúng ta sẽ tuần tự hóa và khôi phục một đối tượng. SavedGameSavedGameTuần tự hóa và giải tuần tự hóa trong Java - 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();


   }
}
Và đây là kết quả: SavingGame{territoriesInfo=null, ResourcesInfo=ResourcesInfo{info='Tây Ban Nha có 100 vàng, Nga có 80 vàng, Pháp có 90 vàng'}, ngoại giaoInfo=DiplomacyInfo{info='Pháp đang có chiến tranh với Nga, Tây Ban Nha đã giữ quan điểm trung lập'}} Đồng thời, chúng tôi nhận được câu trả lời cho câu hỏi giá trị nào sẽ được gán transientcho -field. Nó được gán một giá trị mặc định. Trong trường hợp các đối tượng thì đây là null. Khi rảnh rỗi, bạn có thể đọc bài viết tuyệt vời này về tuần tự hóa . Nó cũng nói về giao diện Externalizablemà chúng ta sẽ nói đến trong bài giảng tiếp theo. Ngoài ra, có một chương về chủ đề này trong cuốn sách “Head-First Java”, hãy chú ý đến nó :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION