序列化和反序列化的詳解
一、基本概念
1、序列化和反序列化的定義:
(1)Java序列化就是指把Java物件轉換為位元組序列的過程
Java反序列化就是指把位元組序列恢復為Java物件的過程。
(2)序列化最重要的作用:在傳遞和儲存物件時.保證物件的完整性和可傳遞性。物件轉換為有序位元組流,以便在網路上傳輸或者儲存在本地檔案中。
反序列化的最重要的作用:根據位元組流中儲存的物件狀態及描述資訊,通過反序列化重建物件。
總結:核心作用就是物件狀態的儲存和重建。(整個過程核心點就是位元組流中所儲存的物件狀態及描述資訊)
2、json/xml的資料傳遞:
在資料傳輸(也可稱為網路傳輸)前,先通過序列化工具類將Java物件序列化為json/xml檔案。
在資料傳輸(也可稱為網路傳輸)後,再將json/xml檔案反序列化為對應語言的物件
3、序列化優點:
①將物件轉為位元組流儲存到硬碟上,當JVM停機的話,位元組流還會在硬碟上默默等待,等待下一次JVM的啟動,把序列化的物件,通過反序列化為原來的物件,並且序列化的二進位制序列能夠減少儲存空間(永久性儲存物件)。
②序列化成位元組流形式的物件可以進行網路傳輸(二進位制形式),方便了網路傳輸。
③通過序列化可以在程序間傳遞物件。
4、序列化演算法需要做的事:
① 將物件例項相關的類元資料輸出。
② 遞迴地輸出類的超類描述直到不再有超類。
③ 類元資料輸出完畢後,從最頂端的超類開始輸出物件例項的實際資料值。
④ 從上至下遞迴輸出例項的資料。
二、Java實現序列化和反序列化的過程
1、實現序列化的必備要求:
只有實現了Serializable或者Externalizable介面的類的物件才能被序列化為位元組序列。(不是則會丟擲異常)
2、JDK中序列化和反序列化的API:
①java.io.ObjectInputStream:物件輸入流。
該類的readObject()方法從輸入流中讀取位元組序列,然後將位元組序列反序列化為一個物件並返回。
②java.io.ObjectOutputStream:物件輸出流。
該類的writeObject(Object obj)方法將將傳入的obj物件進行序列化,把得到的位元組序列寫入到目標輸出流中進行輸出。
3、實現序列化和反序列化的三種實現:
①若Student類僅僅實現了Serializable介面,則可以按照以下方式進行序列化和反序列化。
ObjectOutputStream採用預設的序列化方式,對Student物件的非transient的例項變數進行序列化。
ObjcetInputStream採用預設的反序列化方式,對Student物件的非transient的例項變數進行反序列化。
②若Student類僅僅實現了Serializable介面,並且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則採用以下方式進行序列化與反序列化。
ObjectOutputStream呼叫Student物件的writeObject(ObjectOutputStream out)的方法進行序列化。
ObjectInputStream會呼叫Student物件的readObject(ObjectInputStream in)的方法進行反序列化。
③若Student類實現了Externalnalizable介面,且Student類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進行序列化與反序列化。
ObjectOutputStream呼叫Student物件的writeExternal(ObjectOutput out))的方法進行序列化。
ObjectInputStream會呼叫Student物件的readExternal(ObjectInput in)的方法進行反序列化。
4、序列化和反序列化程式碼示例
public class SerializableTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
FileOutputStream fos = new FileOutputStream("object.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student student1 = new Student("lihao", "wjwlh", "21");
oos.writeObject(student1);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("object.out");
ObjectInputStream ois = new ObjectInputStream(fis);
Student student2 = (Student) ois.readObject();
System.out.println(student2.getUserName()+ " " +
student2.getPassword() + " " + student2.getYear());
}
}
public class Student implements Serializable{
private static final long serialVersionUID = -6060343040263809614L;
private String userName;
private String password;
private String year;
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setPassword(String password) {
this.password = password;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public Student(String userName, String password, String year) {
this.userName = userName;
this.password = password;
this.year = year;
}
}
①序列化圖示
②反序列化圖示
三、序列化和反序列化的注意點:
①序列化時,只對物件的狀態進行儲存,而不管物件的方法;
②當一個父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable介面;
③當一個物件的例項變數引用其他物件,序列化該物件時也把引用物件進行序列化;
④並非所有的物件都可以序列化,至於為什麼不可以,有很多原因了,比如:
-
安全方面的原因,比如一個物件擁有private,public等field,對於一個要傳輸的物件,比如寫到檔案,或者進行RMI傳輸等等,在序列化進行傳輸的過程中,這個物件的private等域是不受保護的;
-
資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者儲存,也無法對他們進行重新的資源分配,而且,也是沒有必要這樣實現;
⑤宣告為static和transient型別的成員資料不能被序列化。因為static代表類的狀態,transient代表物件的臨時資料。
⑥序列化執行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯,該序列號在反序列化過程中用於驗證序列化物件的傳送者和接收者是否為該物件載入了與序列化相容的類。為它賦予明確的值。顯式地定義serialVersionUID有兩種用途:
-
在某些場合,希望類的不同版本對序列化相容,因此需要確保類的不同版本具有相同的serialVersionUID;
-
在某些場合,不希望類的不同版本對序列化相容,因此需要確保類的不同版本具有不同的serialVersionUID。
⑦Java有很多基礎類已經實現了serializable介面,比如String,Vector等。但是也有一些沒有實現serializable介面的;
⑧如果一個物件的成員變數是一個物件,那麼這個物件的資料成員也會被儲存!這是能用序列化解決深拷貝的重要原因;
注意:淺拷貝請使用Clone介面的原型模式。