1. 程式人生 > >深入理解java的可序列化

深入理解java的可序列化

關於Java序列化的文章早已是汗牛充棟了,本文是對我個人過往學習,理解及應用Java序列化的一個總結。此文內容涉及Java序列化的基本原理,以及多種方法對序列化形式進行定製。在撰寫本文時,既參考了Thinking in Java, Effective Java,JavaWorld,developerWorks中的相關文章和其它網路資料,也加入了自己的實踐經驗與理解,文、碼並茂,希望對大家有所幫助。(持續更新中,2012.02.13最後更新)

1. 什麼是Java物件序列化

Java平臺允許我們在記憶體中建立可複用的Java物件,但一般情況下,只有當JVM處於執行時,這些物件才可能存在,即,這些物件的生命週期不會比JVM的生命週期更長。但在現實應用中,就可能要求在JVM停止執行之後能夠儲存(持久化)指定的物件,並在將來重新讀取被儲存的物件。Java物件序列化就能夠幫助我們實現該功能。

使用Java物件序列化,在儲存物件時,會把其狀態儲存為一組位元組,在未來,再將這些位元組組裝成物件。必須注意地是,物件序列化儲存的是物件的"狀態",即它的成員變數。由此可知,物件序列化不會關注類中的靜態變數。

除了在持久化物件時會用到物件序列化之外,當使用RMI(遠端方法呼叫),或在網路中傳遞物件時,都會用到物件序列化。Java序列化API為處理物件序列化提供了一個標準機制,該API簡單易用,在本文的後續章節中將會陸續講到。

2. 簡單示例

在Java中,只要一個類實現了java.io.Serializable介面,那麼它就可以被序列化。此處將建立一個可序列化的類Person,本文中的所有示例將圍繞著該類或其修改版。

Gender類,是一個列舉型別,表示性別

  1. publicenum Gender {  
  2.     MALE, FEMALE  

如果熟悉Java列舉型別的話,應該知道每個列舉型別都會預設繼承類java.lang.Enum,而該類實現了Serializable介面,所以列舉型別物件都是預設可以被序列化的。

Person類,實現了Serializable介面,它包含三個欄位:name,String型別;age,Integer型別;gender,Gender型別。另外,還重寫該類的toString()方法,以方便列印Person例項中的內容。

  1. publicclass Person 
    implements Serializable {  
  2. private String name = null;  
  3. private Integer age = null;  
  4. private Gender gender = null;  
  5. public Person() {  
  6.         System.out.println("none-arg constructor");  
  7.     }  
  8. public Person(String name, Integer age, Gender gender) {  
  9.         System.out.println("arg constructor");  
  10. this.name = name;  
  11. this.age = age;  
  12. this.gender = gender;  
  13.     }  
  14. public String getName() {  
  15. return name;  
  16.     }  
  17. publicvoid setName(String name) {  
  18. this.name = name;  
  19.     }  
  20. public Integer getAge() {  
  21. return age;  
  22.     }  
  23. publicvoid setAge(Integer age) {  
  24. this.age = age;  
  25.     }  
  26. public Gender getGender() {  
  27. return gender;  
  28.     }  
  29. publicvoid setGender(Gender gender) {  
  30. this.gender = gender;  
  31.     }  
  32. @Override
  33. public String toString() {  
  34. return"[" + name + ", " + age + ", " + gender + "]";  
  35.     }  

SimpleSerial,是一個簡單的序列化程式,它先將一個Person物件儲存到檔案person.out中,然後再從該檔案中讀出被儲存的Person物件,並列印該物件。

  1. publicclass SimpleSerial {  
  2. publicstaticvoid main(String[] args) throws Exception {  
  3.         File file = new File("person.out");  
  4.         ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));  
  5.         Person person = new Person("John"101, Gender.MALE);  
  6.         oout.writeObject(person);  
  7.         oout.close();  
  8.         ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));  
  9.         Object newPerson = oin.readObject(); // 沒有強制轉換到Person型別
  10.         oin.close();  
  11.         System.out.println(newPerson);  
  12.     }  

上述程式的輸出的結果為:

  1. arg constructor  
  2. [John, 31, MALE] 

此時必須注意的是,當重新讀取被儲存的Person物件時,並沒有呼叫Person的任何構造器,看起來就像是直接使用位元組將Person物件還原出來的。

當Person物件被儲存到person.out檔案中之後,我們可以在其它地方去讀取該檔案以還原物件,但必須確保該讀取程式的CLASSPATH中包含有Person.class(哪怕在讀取Person物件時並沒有顯示地使用Person類,如上例所示),否則會丟擲ClassNotFoundException。

3. Serializable的作用

為什麼一個類實現了Serializable介面,它就可以被序列化呢?在上節的示例中,使用ObjectOutputStream來持久化物件,在該類中有如下程式碼:

  1. privatevoid writeObject0(Object obj, boolean unshared) throws IOException {  
  2.       ...
  3. if (obj instanceof String) {  
  4.         writeString((String) obj, unshared);  
  5.     } elseif (cl.isArray()) {  
  6.         writeArray(obj, desc, unshared);  
  7.     } elseif (obj instanceof Enum) {  
  8.         writeEnum((Enum) obj, desc, unshared);  
  9.     } elseif (obj instanceof Serializable) {  
  10.         writeOrdinaryObject(obj, desc, unshared);  
  11.     } else {  
  12. if (extendedDebugInfo) {  
  13. thrownew NotSerializableException(cl.getName() + "\n"
  14.                     + debugInfoStack.toString());  
  15.         } else {  
  16. thrownew NotSerializableException(cl.getName());  
  17.         }  
  18.     }  
  19.     ...  

從上述程式碼可知,如果被寫物件的型別是String,或陣列,或Enum,或Serializable,那麼就可以對該物件進行序列化,否則將丟擲NotSerializableException。

4. 預設序列化機制

如果僅僅只是讓某個類實現Serializable介面,而沒有其它任何處理的話,則就是使用預設序列化機制。使用預設機制,在序列化物件時,不僅會序列化當前物件本身,還會對該物件引用的其它物件也進行序列化,同樣地,這些其它物件引用的另外物件也將被序列化,以此類推。所以,如果一個物件包含的成員變數是容器類物件,而這些容器所含有的元素也是容器類物件,那麼這個序列化的過程就會較複雜,開銷也較大。

5. 影響序列化

在現實應用中,有些時候不能使用預設序列化機制。比如,希望在序列化過程中忽略掉敏感資料,或者簡化序列化過程。下面將介紹若干影響序列化的方法。

5.1 transient關鍵字

當某個欄位被宣告為transient後,預設序列化機制就會忽略該欄位。此處將Person類中的age欄位宣告為transient,如下所示,

  1. publicclass Person implements Serializable {  
  2.     ...  
  3. transientprivate Integer age = null;  
  4.     ...  

再執行SimpleSerial應用程式,會有如下輸出:

  1. arg constructor  
  2. [John, null, MALE] 

可見,age欄位未被序列化。

5.2 writeObject()方法與readObject()方法

對於上述已被宣告為transitive的欄位age,除了將transitive關鍵字去掉之外,是否還有其它方法能使它再次可被序列化?方法之一就是在Person類中新增兩個方法:writeObject()與readObject(),如下所示:

  1. publicclass Person implements Serializable {  
  2.     ...  
  3. transientprivate Integer age = null;  
  4.     ...  
  5. privatevoid writeObject(ObjectOutputStream out) throws IOException {  
  6.         out.defaultWriteObject();  
  7.         out.writeInt(age);  
  8.     }  
  9. privatevoid readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
  10.         in.defaultReadObject();  
  11.         age = in.readInt();  
  12.     }  

在writeObject()方法中會先呼叫ObjectOutputStream中的defaultWriteObject()方法,該方法會執行預設的序列化機制,如5.1節所述,此時會忽略掉age欄位。然後再呼叫writeInt()方法顯示地將age欄位寫入到ObjectOutputStream中。readObject()的作用則是針對物件的讀取,其原理與writeObject()方法相同。再次執行SimpleSerial應用程式,則又會有如下輸出:

  1. 相關推薦

    深入理解Java物件序列

    關於Java序列化的文章早已是汗牛充棟了,本文是對我個人過往學習,理解及應用Java序列化的一個總結。此文內容涉及Java序列化的基本原理,以及多種方法對序列化形式進行定製。在撰寫本文時,既參考了Thinking in Java, Effective Java,JavaWorld,developerWor

    由Typecho 深入理解PHP反序列漏洞

    前言 Typecho是一個輕量版的部落格系統,前段時間爆出getshell漏洞,網上也已經有相關的漏洞分析釋出。這個漏洞是由PHP反序列化漏洞造成的,所以這裡我們分析一下這個漏洞,並藉此漏洞深入理解PHP反序列化漏洞。 一、 PHP反序列化漏洞 1.1 漏洞簡介 PH

    理解Java物件序列

    1. 什麼是Java物件序列化     Java平臺允許我們在記憶體中建立可複用的Java物件,但一般情況下,只有當JVM處於執行時,這些物件才可能存在,即,這些物件的生命週期不會比JVM的生命週期更長。但在現實應用中,就可能要求在JVM停止執行之後能夠儲存(持久化)

    理解Java物件序列——Serializable介面

    概述:當一個類實現了Serializable介面(該介面僅為標記介面,不包含任何方法定義),表示該類可以序列化.序列化的目的是將一個實現了Serializable介面的物件轉換成一個位元組序列,可以。 把該位元組序列儲存起來(例如:儲存在一個檔案裡),以後可以隨時將該位元組序列恢復為原來的物件。甚至可以將該位

    java理解Java物件序列

    原文地址:http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 關於Java序列化的文章早已是汗牛充棟了,本文是對我個人過往學習,理解及應用Java序列化的一個總結。此文內容涉及Java序列

    深入理解java序列

    關於Java序列化的文章早已是汗牛充棟了,本文是對我個人過往學習,理解及應用Java序列化的一個總結。此文內容涉及Java序列化的基本原理,以及多種方法對序列化形式進行定製。在撰寫本文時,既參考了Thinking in Java, Effective Java,JavaWorld,develope

    深入理解JAVA序列

    如果你只知道實現 Serializable 介面的物件,可以序列化為本地檔案。那你最好再閱讀該篇文章,文章對序列化進行了更深一步的討論,用實際的例子程式碼講述了序列化的高階認識,包括父類序列化的問題、靜態變數問題、transient 關鍵字的影響、序列化 ID 問題。在筆

    深入理解Java序列和反序列

    序列化是一種物件持久化的手段。普遍應用在網路傳輸、RMI等場景中。本文通過分析ArrayList的序列化來介紹Java序列化的相關內容。主要涉及到以下幾個問題: 怎麼實現Java的序列化 為什麼實現了java.io.Serializable接口才能被序列化 trans

    深入理解Java對象的創建過程:類的初始與實例

    fcm 創建過程 this 創作 alt sso sdn 限定 知識 轉載自:https://blog.csdn.net/justloveyou_/article/details/72466416 摘要:   在Java中,一個對象在可以被使用之前必須要被正確地初始化,這一

    深入理解Java中的同步靜態方法和synchronized(class)程式碼塊的類鎖 深入理解Java併發synchronized同步的程式碼塊不是this物件時的操作

    一.回顧學習內容  在前面幾篇部落格中我我們已經理解了synchronized物件鎖、物件鎖的重入、synchronized方法塊、synchronized非本物件的程式碼塊,  連結:https://www.cnblogs.com/SAM-CJM/category/1314992.h

    深入理解Java併發synchronized同步的程式碼塊不是this物件時的操作

    一.明確一點synchronized同步的是物件不是方法也不是程式碼塊   我有關synchronized同步的是物件討論的部落格在這裡:https://www.cnblogs.com/SAM-CJM/p/9798263.html  只要明確了synchroni

    【學習筆記】 唐大仕—Java程式設計 第5講 深入理解Java語言之5.3 物件構造與初始

    物件構造與初始化 構造方法 構造方法(constructor) 物件都有構造方法 如果沒有,編譯器加一個default構造方法 抽象類(abstract)有沒有構造方法? 答案:抽象類也有構造方法。實際上,任何類都有自己的構造方法

    深入理解Java物件的建立過程:類的初始與例項

    摘要:   在Java中,一個物件在可以被使用之前必須要被正確地初始化,這一點是Java規範規定的。在例項化一個物件時,JVM首先會檢查相關型別是否已經載入並初始化,如果沒有,則JVM立即進行載入並呼叫類構造器完成類的初始化。在類初始化過程中或初始化完畢後,根據具體情況才會

    阿里P7帶你深入理解Java虛擬機器總結——類初始過程

    類的初始化過程 非法向前引用 編譯器手機的順序是由語句在原始檔中出現的順序決定的,靜態語句塊中只能訪問到定義在靜態語句之前的變數,定義它之後的變數,可以賦值,但不能訪問 public class Test{ static{ i=0; system.out.print(

    深入理解java虛擬機器(三)(一個類載入器只初始一次類物件,不同類載入器可以對同一類物件進行初始

    package com.ygl; class Final{public static final int x=6/3;//此處x在編譯時能計算出值,是編譯時的常量,則System.out.println(Final.x);直接輸出值,不再執行下面static(前提是fina

    java物件序列理解

    JAVA RMI(Remote Method Invocation)遠端方法呼叫 在RMI 中物件和簡單的資料值可以作為方法呼叫的引數和結果傳遞。一個物件就是java類的例項。 java序列化: 將一個物件或一組有關的物件做成適合於磁碟儲存或訊息傳

    深入理解Java虛擬機器-類載入連線和初始解析

    不管學習什麼,我一直追求的是知其然,還要知其所以然,對真理的追求可以體現在方方面面。人生短短數十載,匆匆一世似煙雲,我認為,既然來了,就應該留下一些有意義的東西。本系列文章是結合張龍老師的《深入理解JVM》視訊做的一個筆記,其中將自己在學習過程中的實踐記錄、思考理解整合在了一起。希望在鞏固自己的知識時讓更多的

    深入理解Java虛擬機- 學習筆記 - 虛擬機類加載機制

    支持 pub eth 獲取 事件 必須 string 沒有 字節碼 虛擬機把描述類的數據從Class文件加載道內存,並對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。在Java裏,類型的加載、連接和初始化過程都是在程序

    JVM運行時數據區--深入理解Java虛擬機 讀後感

    出棧 很好 棧幀 最大 出錯 生命周期 所有 img 就會 程序計數器 程序計數器是線程私有的區域,很好理解嘛~,每個線程當然得有個計數器記錄當前執行到那個指令。占用的內存空間小,可以把它看成是當前線程所執行的字節碼的行號指示器。如果線程在執行Java方法

    深入理解JAVA集合系列四:ArrayList源碼解讀

    結束 了解 數組下標 size new 數組元素 開始 ini rem 在開始本章內容之前,這裏先簡單介紹下List的相關內容。 List的簡單介紹 有序的collection,用戶可以對列表中每個元素的插入位置進行精確的控制。用戶可以根據元素的整數索引(在列表中的位置)訪