CodeGym /Blog Java /Ngẫu nhiên /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
CHÀO! Trong bài học hôm nay, chúng ta sẽ nói về serialization và deserialization trong Java. Chúng ta sẽ bắt đầu với một ví dụ đơn giản. Hãy tưởng tượng rằng bạn là một nhà phát triển trò chơi máy tính. Nếu bạn lớn lên trong 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 đó!Tuần tự hóa và giải tuần tự hóa trong Java - 1Tôi sợ rằng một trò chơi ngày nay không có những khả năng này sẽ bị diệt vong! Dù sao thì, 'lưu' và 'tải' một trò chơi nghĩa là gì? Chà, chúng tôi hiểu ý nghĩa hàng ngày: 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 "điểm kiểm tra" nhất định mà chúng tôi sử dụng sau này để tải trò chơi. Nhưng điều đó 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 lưu trạng thái của chương trình. Giả sử bạn đang chơi trò chơi chiến lược với Tây Ban Nha. Trò chơi của bạn có trạng thái: mọi người có lãnh thổ nào, mọi người có bao nhiêu tài nguyên, liên minh nào tồn tại và với ai, 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, bằng cách nào đó phải được lưu lại để khôi phục dữ liệu và tiếp tục trò chơi. Khi nó xảy ra, Tuần tự hóa trong Java là quá trình lưu trạng thái của một đối tượng dưới dạng một chuỗi byte. Deserialization trong Java là 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 này? 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 nhau, trao đổi dữ liệu, v.v. Định dạng byte thuận tiện và hiệu quả cho việc này. Ví dụ: chúng tôi có thể chuyển đổi một đối tượng của Trò chơi đã lưu của chúng tôithành một chuỗi các byte, chuyển các byte này qua mạng sang một máy tính khác, sau đó trên máy tính kia chuyển đổi các byte này trở lại thành một đối tượng Java! Nghe có vẻ khó nhỉ? Có vẻ như khó có thể làm nên chuyện này :/ Mừng là không phải vậy! :) Trong Java, giao diện Serializable 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 phải triển khai một phương thức duy nhất để sử dụng nó! Hãy xem lớp của chúng ta lưu trò chơi đơ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[] 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) +
               '}';
   }
}
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, và giao diện Serializable cho máy Java biết: ' mọi thứ đều ổn nếu 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 đó đã được đưa ra ở trên: chỉ cần cung cấp thông tin cần thiết cho máy Java. Trong một bài học trước, 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. Đây là Serializable — một giao diện như vậy. Đây là một điểm quan trọng khác: Tại sao chúng ta cầnBiến serialVersionUID dài cuối cùng tĩnh riêng tư mà chúng ta đã định nghĩa trong lớp? 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. Mỗi lớp thực hiện giao diện Nối tiếp có một mã định danh phiên bản. Nó được xác định dựa trên nội dung của lớp — các trường và thứ tự khai báo của chúng, các phương thức và thứ tự khai báo của chúng. 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ì mã định danh 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 tôi cố gắng giải tuần tự hóa, tức là khôi phục một đối tượng từ chuỗi byte, giá trị của serialVersionUID được so sánh với giá trị của serialVersionUIDcủa lớp trong chương trình của chúng tôi. Nếu các giá trị không khớp, thì java.io.InvalidClassException sẽ bị 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 số nhận dạng phiên bản 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ể sử dụng 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 SavingGame 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[] 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();
   }
}
Như bạn có thể thấy, chúng tôi đã tạo 2 luồng: FileOutputStreamObjectOutputStream . Cái đầu tiên biết cách ghi dữ liệu vào tệp và cái thứ hai chuyển đổi các đối tượng thành byte. Bạn đã thấy các cấu trúc lồng nhau tương tự, ví dụ: new BufferedReader(new InputStreamReader(...)) , trong các bài học trước, vì vậy chúng sẽ không làm bạn sợ :) Bằng cách tạo chuỗi hai luồng này, chúng ta thực hiện cả hai tác vụ: chúng tôi chuyển đổi đối tượng SavingGame thành một chuỗi 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 nhìn vào những gì chúng tôi có! Đã đến lúc xem hồ sơ! *Lưu ý: không cần tạo file trước. Nếu một tệp có tên được chỉ định không tồn tại, thì nó sẽ được tạo tự động* Và đây là nội dung của nó: ¬н sr SavingGame [ngoại giaoInfot [Ljava/lang/String;[ resourceInfoq ~ [ lãnh thổInfoq ~ xpur [Ljava.lang. String;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°РЅР ёСЏ заняла позицию нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 Người chơi Ồ, ồ :( Có vẻ như chương trình của chúng tôi không hoạt động : ( Thực ra, nó đã hoạt động. Bạn có nhớ rằng chúng tôi đã gửi một chuỗi byte, chứ không chỉ đơn giản là một đối tượng hoặc văn bản, tới tệp không? Chà, đây là chuỗi byte này trông giống như vậ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 của chúng ta:

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ả! Đã lưuGame{territoryInfo=[Tây Ban Nha có 6 tỉnh, Nga có 10 tỉnh, Pháp có 8 tỉnh], resourceInfo=[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 đã giữ vị trí trung lập]} 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 trò chơi đó từ tệp. Bây giờ, hãy thử làm điều tương tự, nhưng chúng tôi sẽ xóa mã định danh phiên bản khỏi lớp Trò chơi đã lưu của chúng tôi . 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ống nhau. Chúng tôi sẽ chỉ xóa private static serialVersionUID cuối cùng khỏi lớp SavingGame . Đây là đối tượng của chúng tôi sau khi tuần tự hóa: ¬н sr Đã lưuGameі€MіuОm‰ [ ngoại giaoInfot [Ljava/lang/String;[ resourceInfoq ~ [ TerritoryInfoq ~ 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ì xảy ra khi chúng ta cố gắng giải tuần tự hóa nó: InvalidClassException: lớp cục bộ không tương thích: luồng classdesc serialVersionUID = -196410440475012755, lớp cục bộ serialVersionUID = -6675950253085108747 Nhân tiện, chúng tôi đã bỏ lỡ một điều quan trọng. Rõ ràng, các chuỗi và nguyên thủy được sắp xếp theo thứ tự một cách dễ dàng: Java chắc chắn có một số cơ chế tích hợp sẵn cho việc này. Nhưng nếu lớp có thể tuần tự hóa 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 lớp TerritoryInfo , ResourceInfoDiplomacyInfo riêng biệt để làm việc với lớp SavingGame của chúng ta .

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 + '\'' +
               '}';
   }
}
Và bây giờ chúng ta phải đối mặt với một câu hỏi: Tất cả các lớp này có phải là Có thể tuần tự hóa nếu chúng ta muốn tuần tự hóa lớp SavingGame của mình không ?

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 +
               '}';
   }
}
Thôi được! Hãy thử nghiệm nó! Hiện tại, chúng tôi sẽ để nguyên mọi thứ và cố gắng tuần tự hóa một đối tượng SavingGame :

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();
   }
}
Kết quả: Ngoại lệ trong luồng "chính" java.io.NotSerializableException: DiplomacyInfo Nó không hoạt động! Vì vậy, đây 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 đối tượng 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ứ thế mãi mãi. Tất cả các lớp trong chuỗi này phải được tuần tự hóa , 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 trong quá trình tuần tự hóa? Hoặc điều gì sẽ xảy ra nếu chúng ta có lớp TerritoryInfo 'thông qua kế thừa' như một phần của thư viện? Và giả sử xa hơn rằng nó không phải làvà theo đó, chúng ta không thể thay đổi nó. Điều đó có nghĩa là chúng ta không thể thêm trường TerritoryInfo vào lớp Trò chơi đã lưu của mình , bởi vì khi đó toàn bộ lớp Trò chơi đã lưu sẽ trở nên không thể sắp xếp được! Đó là một vấn đề: / Tuần tự hóa và giải tuần tự hóa trong Java - 2Trong Java, loại vấn đề này được giải quyết bằng từ khóa thoáng qua . 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ử đặt một trong các trường của lớp SavingGame của chúng ta thành tạm thời , sau đó chúng ta sẽ sắp xếp theo thứ tự và khôi phục một đối tượng.

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();


   }
}
Và đây là kết quả: SavingGame{territoryInfo=null, resourceInfo=ResourceInfo{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ữ vị trí trung lập'}} Như đã nói, chúng ta đã có câu trả lời cho câu hỏi giá trị nào sẽ được gán cho trường tạm thời . 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 một chương hay 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