序列化和反序列化的底層實現原理是什麼

序列化和反序列化做爲Java裏一個較爲基礎的知識點,那你能說一下序列化和反序列化底層是如何實現的嗎?html

1、基本概念

一、什麼是序列化和反序列化java

(1)Java序列化是指把Java對象轉換爲字節序列的過程,而Java反序列化是指把字節序列恢復爲Java對象的過程;算法

(2)序列化:對象序列化的最主要的用處就是在傳遞和保存對象的時候,保證對象的完整性和可傳遞性。序列化是把對象轉換成有序字節流,以便在網絡上傳輸或者保存在本地文件中。序列化後的字節流保存了Java對象的狀態以及相關的描述信息。序列化機制的核心做用就是對象狀態的保存與重建。數據庫

(3)反序列化:客戶端從文件中或網絡上得到序列化後的對象字節流後,根據字節流中所保存的對象狀態及描述信息,經過反序列化重建對象。安全

(4)本質上講,序列化就是把實體對象狀態按照必定的格式寫入到有序字節流,反序列化就是從有序字節流重建對象,恢復對象狀態。網絡

二、爲何須要序列化與反序列化socket

咱們知道,當兩個進程進行遠程通訊時,能夠相互發送各類類型的數據,包括文本、圖片、音頻、視頻等, 而這些數據都會以二進制序列的形式在網絡上傳送。ide

那麼當兩個Java進程進行通訊時,可否實現進程間的對象傳送呢?答案是能夠的!如何作到呢?這就須要Java序列化與反序列化了!this

換句話說,一方面,發送方須要把這個Java對象轉換爲字節序列,而後在網絡上傳送;另外一方面,接收方須要從字節序列中恢復出Java對象。spa

當咱們明晰了爲何須要Java序列化和反序列化後,咱們很天然地會想Java序列化的好處。其好處一是實現了數據的持久化,經過序列化能夠把數據永久地保存到硬盤上(一般存放在文件裏),二是,利用序列化實現遠程通訊,即在網絡上傳送對象的字節序列。

總的來講能夠歸結爲如下幾點:

(1)永久性保存對象,保存對象的字節序列到本地文件或者數據庫中; 
(2)經過序列化以字節流的形式使對象在網絡中進行傳遞和接收; 
(3)經過序列化在進程間傳遞對象;

三、序列化算法通常會按步驟作以下事情:

(1)將對象實例相關的類元數據輸出。 
(2)遞歸地輸出類的超類描述直到再也不有超類。 
(3)類元數據完了之後,開始從最頂層的超類開始輸出對象實例的實際數據值。 
(4)從上至下遞歸輸出實例的數據

2、Java如何實現序列化和反序列化

一、JDK類庫中序列化和反序列化API

(1)java.io.ObjectOutputStream:表示對象輸出流;

它的writeObject(Object obj)方法能夠對參數指定的obj對象進行序列化,把獲得的字節序列寫到一個目標輸出流中;

(2)java.io.ObjectInputStream:表示對象輸入流;

它的readObject()方法源輸入流中讀取字節序列,再把它們反序列化成爲一個對象,並將其返回;

二、實現序列化的要求

只有實現了Serializable或Externalizable接口的類的對象才能被序列化,不然拋出異常!

三、實現Java對象序列化與反序列化的方法

假定一個User類,它的對象須要序列化,能夠有以下三種方法:

(1)若User類僅僅實現了Serializable接口,則能夠按照如下方式進行序列化和反序列化

ObjectOutputStream採用默認的序列化方式,對User對象的非transient的實例變量進行序列化。 
ObjcetInputStream採用默認的反序列化方式,對對User對象的非transient的實例變量進行反序列化。

(2)若User類僅僅實現了Serializable接口,而且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則採用如下方式進行序列化與反序列化。

ObjectOutputStream調用User對象的writeObject(ObjectOutputStream out)的方法進行序列化。 
ObjectInputStream會調用User對象的readObject(ObjectInputStream in)的方法進行反序列化。

(3)若User類實現了Externalnalizable接口,且User類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照如下方式進行序列化與反序列化。

ObjectOutputStream調用User對象的writeExternal(ObjectOutput out))的方法進行序列化。 
ObjectInputStream會調用User對象的readExternal(ObjectInput in)的方法進行反序列化。

四、JDK類庫中序列化的步驟

步驟一:建立一個對象輸出流,它能夠包裝一個其它類型的目標輸出流,如文件輸出流:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\object.out"));

步驟二:經過對象輸出流的writeObject()方法寫對象:

oos.writeObject(new User("xuliugen", "123456", "male"));

五、JDK類庫中反序列化的步驟

步驟一:建立一個對象輸入流,它能夠包裝一個其它類型輸入流,如文件輸入流:

ObjectInputStream ois= new ObjectInputStream(new FileInputStream("object.out"));

步驟二:經過對象輸出流的readObject()方法讀取對象:

User user = (User) ois.readObject();

說明:爲了正確讀取數據,完成反序列化,必須保證向對象輸出流寫對象的順序與從對象輸入流中讀對象的順序一致。

六、序列化和反序列化的示例

爲了更好地理解Java序列化與反序列化,舉一個簡單的示例以下:

 

 1 public class SerialDemo {
 2  
 3  
 4  
 5 public static void main(String[] args) throws IOException, ClassNotFoundException {
 6  
 7 //序列化
 8  
 9 FileOutputStream fos = new FileOutputStream("object.out");
10  
11 ObjectOutputStream oos = new ObjectOutputStream(fos);
12  
13 User user1 = new User("xuliugen", "123456", "male");
14  
15 oos.writeObject(user1);
16  
17 oos.flush();
18  
19 oos.close();
20  
21 //反序列化
22  
23 FileInputStream fis = new FileInputStream("object.out");
24  
25 ObjectInputStream ois = new ObjectInputStream(fis);
26  
27 User user2 = (User) ois.readObject();
28  
29 System.out.println(user2.getUserName()+ " " +
30  
31 user2.getPassword() + " " + user2.getSex());
32  
33 //反序列化的輸出結果爲:xuliugen 123456 male
34  
35 }
36  
37 }
38  
39  
40  
41 public class User implements Serializable {
42  
43 private String userName;
44  
45 private String password;
46  
47 private String sex;
48  
49 //全參構造方法、get和set方法省略
50  
51 }

 

object.out文件以下(使用UltraEdit打開):

這裏寫圖片描述

注:上圖中0000000h-000000c0h表示行號;0-f表示列;行後面的文字表示對這行16進制的解釋;對上述字節碼所表述的內容感興趣的能夠對照相關的資料,查閱一下每個字符表明的含義,這裏不在探討!

相似於咱們Java代碼編譯以後的.class文件,每個字符都表明必定的含義。序列化和反序列化的過程就是生成和解析上述字符的過程!

序列化圖示:

這裏寫圖片描述

反序列化圖示:

這裏寫圖片描述

3、相關注意事項

一、序列化時,只對對象的狀態進行保存,而無論對象的方法;

二、當一個父類實現序列化,子類自動實現序列化,不須要顯式實現Serializable接口;

三、當一個對象的實例變量引用其餘對象,序列化該對象時也把引用對象進行序列化;

四、並不是全部的對象均可以序列化,至於爲何不能夠,有不少緣由了,好比:

  • 安全方面的緣由,好比一個對象擁有private,public等field,對於一個要傳輸的對象,好比寫到文件,或者進行RMI傳輸等等,在序列化進行傳輸的過程當中,這個對象的private等域是不受保護的;

  • 資源分配方面的緣由,好比socket,thread類,若是能夠序列化,進行傳輸或者保存,也沒法對他們進行從新的資源分配,並且,也是沒有必要這樣實現;

五、聲明爲static和transient類型的成員數據不能被序列化。由於static表明類的狀態,transient表明對象的臨時數據。

六、序列化運行時使用一個稱爲 serialVersionUID 的版本號與每一個可序列化類相關聯,該序列號在反序列化過程當中用於驗證序列化對象的發送者和接收者是否爲該對象加載了與序列化兼容的類。爲它賦予明確的值。顯式地定義serialVersionUID有兩種用途:

  • 在某些場合,但願類的不一樣版本對序列化兼容,所以須要確保類的不一樣版本具備相同的serialVersionUID;

  • 在某些場合,不但願類的不一樣版本對序列化兼容,所以須要確保類的不一樣版本具備不一樣的serialVersionUID。

七、Java有不少基礎類已經實現了serializable接口,好比String,Vector等。可是也有一些沒有實現serializable接口的;

八、若是一個對象的成員變量是一個對象,那麼這個對象的數據成員也會被保存!這是能用序列化解決深拷貝的重要緣由;

       序列化時,類的全部數據成員應可序列化除了聲明爲transient或static的成員。將變量聲明爲transient告訴JVM咱們會負責將變元序列化。將數據成員聲明爲transient後,序列化過程就沒法將其加進對象字節流中,沒有從transient數據成員發送的數據。後面數據反序列化時,要重建數據成員(由於它是類定義的一部分),但不包含任何數據,由於這個數據成員不向流中寫入任何數據。記住,對象流不序列化static或transient。咱們的類要用writeObject()與readObject()方法以處理這些數據成員。使用writeObject()與readObject()方法時,還要注意按寫入的順序讀取這些數據成員

 

那對於這些問題,咱們該如何進行序列化和反序列化呢?

簡單,也就是說咱們要對這倆個類型的變量單獨處理,怎麼辦?就是在出現這類變量的所屬類中增長倆個方法

 

 1 private void writeObject(java.io.ObjectOutputStream out) throws IOException
 2  
 3 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
 4 而對應於咱們的類中添加的方法就是
 5 
 6  
 7 public class SerialTest extends parent implements Serializable {
 8  
 9 //省略
10  
11 private void writeObject(ObjectOutputStream out) throws IOException {
12  
13 out.defaultWriteObject();
14  
15 out.writeInt(this.testStatic);
16  
17 out.writeInt(this.testTransient);
18  
19 }
20  
21 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
22  
23 in.defaultReadObject();
24  
25 this.testStatic = in.readInt();
26  
27 this.testTransient = in.readInt();
28  
29 }
30  
31  
32  
33 }

 

當ObjectOutputStream對一個SerialTest對象進行序列化時,若是該對象具備writeObject()方法,那麼就會執行這一方法,不然就按默認方式序列化。在該對象的writeObjectt()方法中,能夠先調用ObjectOutputStream的defaultWriteObject()方法,使得對象輸出流先執行默認的序列化操做。同理可得出反序列化的狀況,不過此次是defaultReadObject()方法。

 

   ObjectOutputStream.defaultWriteObject() :將當前類的非靜態(static)和非瞬態字段(transient)寫入此流。

   ObjectInputStream.defaultReadObject() :   今後流讀取當前類的非靜態和非瞬態字段。

 

 

Externalizable的做用

對於實現Serializable的類來講,在序列化的時候,全部的非靜態(static)和非瞬態字段(transient)會被自動序列化,若是有一些特殊要求,咱們能夠徹底手動控制哪些字段要被序列化,哪些不要序列化。將他們的生死大權徹底掌握在咱手中。怎麼辦?這個時候就應該談談Externalizable類了。

 

只要實現Externalizable這個類,而且複寫 

 

readExternal(ObjectInput in) throws IOException,CalssNotFoundException
 
writeExternal(ObjectOutput out) throws IOException,CalssNotFoundException

 

就能夠了,在readExternal(ObjectInput in) throws IOException,CalssNotFoundException方法中,能夠自行決定從in讀取哪些對象數據。

 

writeExternal(ObjectOutput out) throws IOException,CalssNotFoundException方法中,能夠自行決定將什麼數據write到out去。

 

這倆個方法分別會在在ObjectOutputStream.writeObject(object);ObjectInputStream.readObject()自動執行。

 


參考文章:

一、https://zhidao.baidu.com/question/688891250408618484.html 
二、https://blog.csdn.net/morethinkmoretry/article/details/5929345 
三、https://www.jianshu.com/p/edcf7bd2c085 
四、https://blog.csdn.net/xiaocaidexuexibiji/article/details/22692097

相關文章
相關標籤/搜索