walterwing |
|
|||
日歷
統計
導航常用鏈接留言簿(1)隨筆分類隨筆檔案搜索最新評論
閱讀排行榜評論排行榜 |
本文主體內容轉載自http://www.aygfsteel.com/amigoxie/archive/2007/09/16/145465.html,同時也根據自己的學習體會,參考多方面資料,對其加以補充
當兩個進程在進行遠程通信時,彼此可以發送各種類型的數據。無論是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方需要把這個Java對象轉換為字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復為Java對象。 把Java對象轉換為字節序列的過程稱為對象的序列化。 把字節序列恢復為Java對象的過程稱為對象的反序列化。
利用對象序列化可以實現“輕量級持久化”(lightweight persistence)。“持久化”意味著一個對象的生存周期并不取決于程序是否正在執行;它可以生存于程序的調用之間。通過將一個序列化對象寫入磁盤,然后在重新調用時恢復該對象,就能夠實現持久化的效果。之所以稱其為“輕量級”,是因為不能用某種“persistent”(持久)關鍵字來簡單地定義一個對象,并讓系統自動維護其他細節問題(盡管將來有可能實現)。相反,對象必須在程序中顯式地序列化和重組。如果需要一個更嚴格的持久化機制,可以考慮使用Java數據對象(JDO)或者像Hibernate之類的工具 對象序列化的概念加入到語言中是為了提供對兩種主要特性的支持: ·Java的“遠程方法調用”(RMI,Remote Method Invocation)使存活于其他計算機上的對象使用起來就像是存活于本機上一樣。當向遠程對象發送消息時,需要通過對象序列化來傳輸參數和返回值。 ·對Java Beans來說對象序列化也是必需的。使用一個Bean時,一般情況下是在設計階段對它的狀態信息進行配置。這種狀態信息必須保存下來,并在程序啟動以后,進行恢復;具體工作由對象序列化完成。 1) 把對象的字節序列永久地保存到硬盤上,通常存放在一個文件中; 2) 在網絡上傳送對象的字節序列。 一. JDK類庫中的序列化API java.io.ObjectInputStream代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回。 只有實現了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行為,而僅實現Serializable接口的類可以采用默認的序列化方式 。 對象序列化包括如下步驟: 1) 創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,如文件輸出流; 2) 通過對象輸出流的writeObject()方法寫對象。 對象反序列化的步驟如下: 1) 創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,如文件輸入流; 2) 通過對象輸入流的readObject()方法讀取對象。 下面讓我們來看一個對應的例子,類的內容如下: ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() 輸出結果如下: obj1=你好! obj2=Sat Sep 15 22:02:21 CST 2007 obj3=name=阿蜜果, age=24 obj4=123 因此例比較簡單,在此不再詳述。 二 實現Serializable接口 ObjectOutputStream只能對Serializable接口的類的對象進行序列化。默認情況下,ObjectOutputStream按照默認方式序列化,這種序列化方式僅僅對對象的非transient的實例變量進行序列化,而不會序列化對象的transient的實例變量,也不會序列化靜態變量。 1) 如果在內存中對象所屬的類還沒有被加載,那么會先加載并初始化這個類。如果在classpath中不存在相應的類文件,那么會拋出ClassNotFoundException; 2) 在反序列化時不會調用類的任何構造方法(注意與下面Externalizable接口的區別)。 如果用戶希望控制類的序列化方式,可以在可序列化類中提供以下形式的writeObject()和readObject()方法。 private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException; 需要注意的地方是:上面兩個方法并不是Serializable接口定義的,Serializable只是一個標記接口,并沒有任何內容。而且這兩個方法都是private的,但卻并不是被定義這兩個方法的類本身所調用——當ObjectOutputStream對一個Customer對象進行序列化時,如果該對象具有writeObject()方法,那么就會執行這一方法,否則就按默認方式序列化。在該對象的writeObjectt()方法中,可以先調用ObjectOutputStream的defaultWriteObject()方法,使得對象輸出流先執行默認的序列化操作。同理可得出反序列化的情況,不過這次是defaultReadObject()方法。 有些對象中包含一些敏感信息,這些信息不宜對外公開。如果按照默認方式對它們序列化,那么它們的序列化數據在網絡上傳輸時,可能會被不法份子竊取。對于這類信息,可以對它們進行加密后再序列化,在反序列化時則需要解密,再恢復為原來的信息——這是transient關鍵字的第一個用途,屏蔽敏感信息。 transient的第二個功能是在用途時,將某些無需序列化的成員變量設為transient類型,將節省空間和時間,提高序列化的性能。
三 實現Externalizable接口 Externalizable接口繼承自Serializable接口,如果一個類實現了Externalizable接口,那么將完全由這個類控制自身的序列化行為。Externalizable接口聲明了兩個方法: public void writeExternal(ObjectOutput out) throws IOException public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException 前者負責序列化操作,后者負責反序列化操作。 在對實現了Externalizable接口的類的對象進行反序列化時,會先調用類的不帶參數的構造方法,這是有別于默認反序列方式的。如果把類的不帶參數的構造方法刪除,或者把該構造方法的訪問權限設置為private、默認或protected級別,會拋出java.io.InvalidException: no valid constructor異常。
類的serialVersionUID的默認值完全依賴于Java編譯器的實現,對于同一個類,用不同的Java編譯器編譯,有可能會導致不同的serialVersionUID,也有可能相同。為了提高serialVersionUID的獨立性和確定性,強烈建議在一個可序列化類中顯示的定義serialVersionUID,為它賦予明確的值。顯式地定義serialVersionUID有兩種用途: 1) 在某些場合,希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有相同的serialVersionUID; 2) 在某些場合,不希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有不同的serialVersionUID。 五 利用序列化來“克隆”對象 要知道,序列化是對對象的一個“深拷貝”,為此我們完全可以用序列化來克隆一個對象(如果支持的話)。 要克隆序列化對象,簡單地將該對象序列化到輸出流中去,然后再讀取回來。結果就是一個對已經存在的對象進行了深拷貝的新對象。我們不需要將該對象寫入文件中——可以使用ByteArrayOutputStream將數據保存在字節數組中。 但是,這種方法盡管很聰明,但是比創建一個新的對象,然后拷貝或克隆數據字段的克隆方法要慢許多。 例: ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() 六 Preferences JDK 1.4引入了Preferences API,它比對象序列化更接近于持久化,因為它可以自動存儲和讀取信息。不過,它只能用于小的受限的集合——我們只能存儲原始類型和字符串,并且每個字符串的存儲長度不能超過8K。正如其名,Preferences API用于存儲和讀取用戶的Preferences以及程序配置項的設置。 Preferences是一個鍵值集合(類似映射),存儲在一個結點層次結構中。 七 有關序列化的一些最佳實踐(轉自http://java.ccidnet.com/art/3737/20040111/469787_1.html) 1、實現Serializable回導致發布的API難以更改,并且使得package-private和private 這兩個本來封裝的較好的咚咚也不能得到保障了 2、Serializable會為每個類生成一個序列號,生成依據是類名、類實現的接口名、 public和protected方法,所以只要你一不小心改了一個已經publish的API,并且沒有自 己定義一個long類型的叫做serialVersionUID的field,哪怕只是添加一個getXX,就會 讓你讀原來的序列化到文件中的東西讀不出來(不知道為什么要把方法名算進去?) 3、不用構造函數用Serializable就可以構造對象,看起來不大合理,這被稱為 extralinguistic mechanism,所以當實現Serializable時應該注意維持構造函數中所維 持的那些不變狀態 4、增加了發布新版本的類時的測試負擔 5、1.4版本后,JavaBeans的持久化采用基于XML的機制,不再需要Serializable 6、設計用來被繼承的類時,盡量不實現Serializable,用來被繼承的interface也不要 繼承Serializable。但是如果父類不實現Serializable接口,子類很難實現它,特別是 對于父類沒有可以訪問的不含參數的構造函數的時候。所以,一旦你決定不實現 Serializable接口并且類被用來繼承的時候記得提供一個無參數的構造函數 7、不管你選擇什么序列化形式,聲明一個顯式的UID: private static final long serialVersionUID = randomLongValue; 8、不需要序列化的東西使用transient注掉它吧,別什么都留著 9、writeObject/readObject重載以完成更好的序列化 readResolve 與 writeReplace重載以完成更好的維護invariant controllers 八 總結 ·要想序列化,需要聲明實現Serializable接口(Exeternalizable一會再說) ·如果有些東西想藏起來,用transient標識 ·如果想自己處理transient或static成員,自己定義readObject()和writeObject() ·如果完全想自己處理,實現Exeternalizable接口
|
![]() |
|
Copyright © This is Wing | Powered by: 博客園 模板提供:滬江博客 |