在本文中,我們將解釋什麼是序列化以及它在 Java 中的工作原理。
圖 1. 線程的圖形表示
Stream主要分為兩種:
介紹
物件序列化是物件儲存其自身以及它使用輸出流引用的任何其他物件的完整副本的能力(例如,儲存到外部檔案)。這樣,稍後需要時可以從序列化(已儲存的)副本重新建立物件。物件序列化是JDK 1.1中引入的新功能,提供了將群組或單一物件轉換為位元流或位元組數組的功能,以便透過網路儲存或傳輸。如上所述,給定的位元流或位元組數組可以轉換回 Java 物件。這主要歸功於ObjectInputStream
和類而自動發生ObjectOutputStream
。程式設計師可以Serializable
在建立類別時決定透過實作介面來實現此功能。序列化過程也稱為物件封送,而反序列化過程稱為反封送。序列化是一種機制,允許物件使用ObjectOutputStream
. 保存的對象可以是資料結構、圖表、類別JFrame
對像或任何其他對象,無論其類型為何。同時,序列化儲存有關物件類型的信息,以便稍後在反序列化時,該資訊用於重新建立物件的確切類型。因此,序列化提供了以下功能:
- 用於儲存物件的系統,即將其屬性保存到外部檔案、磁碟或資料庫。
- 遠程過程呼叫系統。
- 物件分發系統,例如COM、COBRA等軟體元件中的物件分發系統。
- 用於識別變數資料隨時間變化的系統。
流:
每個程式都必須將其資料寫入儲存位置或管道,並且每個程式都必須從管道或儲存位置讀取資料。在 Java 中,程式寫入和讀取資料的這些通道稱為Streams (Stream
)。
- 稱為 *Streams 的位元組流類
- 稱為 *Reader 和 *Writer 的字元流類
堅持
物件持久性是物件生存的能力,換句話說,是在程式執行過程中「生存」的能力。這意味著,只要不再使用執行時間建立的任何對象,JVM 清除程序就會銷毀該對象。但如果實現了持久化 API,這些物件就不會被 JVM scavenger 銷毀,而是被允許“存活”,這也使得下次應用程式啟動時可以存取它們。換句話說,持久性意味著物件有一個生命週期,與正在運行的應用程式的生命週期無關。實現持久性的一種方法是將物件儲存在外部文件或資料庫中的某個位置,然後稍後使用這些文件或資料庫作為來源來還原它們。這就是序列化發揮作用的地方。只要 JVM 運行,任何非持久性物件就存在。序列化對像只是轉換為流的對象,然後將其保存到外部文件或透過網路傳輸以進行儲存和恢復。Serialized介面的實現
任何類別都必須實作一個介面java.io.Serializable
來序列化該類別的物件。此介面Serializable
沒有方法,僅標記類,以便將其標識為可序列化。只能保存序列化類別物件的欄位。方法或建構子不會儲存為序列化流的一部分。如果任何物件充當對另一個物件的引用,那麼如果該物件的類別實現了該接口,則該物件的欄位也會被序列化Serializable
。換句話說,這樣得到的這個物件的圖是完全可序列化的。物件圖包括物件及其子物件的字段樹或結構。幫助實現該介面的兩個主要類別Seriliazable
:
ObjectInputStream
ObjectOutputStream
import java.io.*;
public class RandomClass implements Serializable {
// Генерация рандомного значения
private static int r() {
return (int)(Math.random() * 10);
}
private int data[];
// Конструктор
public RandomClass() {
datafile = new int[r()];
for (int i=0; i<datafile.length; i++)
datafile[i]=r();
}
public void printout() {
System.out.println("This RandomClass has "+datafile.length+" random integers");
for (int i=0; i<datafile.length; i++) {
System.out.print(datafile[i]+":");
System.out.println();
}
}
在上面的程式碼中,創建了一個可序列化的類,因為 由序列化介面“標記”。當建立該類別的實例時,該類別會建立一個隨機整數數組。下面的程式碼顯示了使用ObjectOutputStream
. 該程式有一個整數數組,但對於序列化,我們不必迭代其內部物件。介面Seriliazable
會自動處理這個問題。 清單 2. 序列化物件以輸出到檔案的簡單範例
import java.io.*;
import java.util.*;
public class OutSerialize {
public static void main (String args[]) throws IOException {
RandomClass rc1 = new RandomClass();
RandomClass rc2 = new RandomClass();
//создание цепи потоков с потоком вывода an object в конце
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("objects.dat"));
Date now = new Date(System.currentTimeMillis());
//java.util.* был импортирован для использования класса Date
out.writeObject(now);
out.writeObject(rc1);
out.writeObject(rc2);
out.close();
System.out.println("I have written:");
System.out.println("A Date object: "+now);
System.out.println("Two Group of randoms");
rc1.printout();
rc2.printout();
}
}
下面的程式碼示範了該類別的功能ObjectInputStream
,它將序列化資料從外部檔案讀取到程式中。請注意,物件的讀取順序與寫入檔案的順序相同。 清單 3. 讀取序列化物件或反序列化
import java.io.*;
import java.util.*;
public class InSerialize {
public static void main (String args[]) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream (new FileInputStream("objects.dat"));
Date d1 = (Date)in.readObject();
RandomClass rc1 = (RandomClass)in.readObject();
RandomClass rc2 = (RandomClass)in.readObject();
System.out.println("I have read:");
System.out.println("A Date object: "+d1);
System.out.println("Two Group of randoms");
rc1.printout();
rc2.printout();
}
}
幾乎所有 Java 類別都可以序列化,包括 AWT 類別。框架是一個窗口,包含一組圖形組件。如果幀被序列化,則序列化引擎會處理此問題並序列化其所有組件和資料(位置、內容等)。某些 Java 類別物件無法序列化,因為它們包含引用臨時作業系統資源的資料。例如類別java.io.FileInputStream
和java.lang.Thread
. 如果一個物件包含對不可序列化元素的引用,則整個序列化操作將失敗並拋出異常NotSerializableException
。如果任何物件引用未序列化物件的引用,則可以使用瞬態關鍵字將其序列化。 清單 4. 使用瞬態關鍵字建立可序列化對象
public class Sclass implements Serializable{
public transient Thread newThread;
//помните, что поток(поток параллельного исполнения) по умолчанию не сериализуемый класс
private String studentID;
private int sum;
}
序列化的安全性
在 Java 中序列化一個類別涉及透過流將其所有資料傳遞到外部文件或資料庫。我們可以隨時限制要序列化的資料。有兩種方法可以做到這一點:- 每個宣告為瞬態的類別參數都不會被序列化(預設情況下,所有類別參數都會被序列化)
- 或者,我們要序列化的類別的每個參數都標有標籤
Externalizable
(預設情況下,不序列化任何參數)。
ObjectOutputStream
當在物件上呼叫時,如果該物件的資料欄位被標記為瞬態,則資料欄位將不會被序列化。例如:private transient String password
。另一方面,要明確地將物件的資料宣告為可序列化,我們必須將該類別標記為ExternalizablewriteExternal
明確readExteranl
寫入和讀取該物件的資料。
結論
物件序列化的特性在許多分散式系統中被用作傳輸資料的一種方式。但序列化揭示了隱藏的細節,從而破壞了抽象資料類型的真實性,進而破壞了封裝性。同時,很高興知道序列化物件的資料與原始物件中的資料相同。這也是實現介面ObjectInputValidation
和重寫方法的絕佳機會validateObject()
,即使使用多行程式碼也是如此。如果沒有找到該對象,那麼我們可以適當地拋出異常InvalidObjectException
。原文:Java 中序列化的工作原理
GO TO FULL VERSION