一、基本概念
1、序列化和反序列化的定義:
- 序列化:Java序列化就是指把Java對象轉換為字節序列的過程
- 反序列化:Java反序列化就是指把字節序列恢復為Java對象的過程。
序列化最重要的作用:
- 把對象的字節序列永久地保存到硬盤上,通常放到一個文件中(持久化對象)
- 在網絡上傳送對象的字節序列(網絡傳輸對象)
反序列化的最重要的作用:根據字節流中保存的對象狀態及描述信息,通過反序列化重建對象。
總結:核心作用就是對象狀態的保存和重建。(整個過程核心點就是字節流中所保存的對象狀態及描述信息)
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接口的原型模式。
參考鏈接:
https://blog.csdn.net/tree_ifconfig/article/details/82766587