walterwing  
          日歷
          <2008年10月>
          2829301234
          567891011
          12131415161718
          19202122232425
          2627282930311
          2345678
          統計
          • 隨筆 - 12
          • 文章 - 1
          • 評論 - 7
          • 引用 - 0

          導航

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

           
          本文主體內容轉載自http://www.aygfsteel.com/amigoxie/archive/2007/09/16/145465.html,同時也根據自己的學習體會,參考多方面資料,對其加以補充



          當兩個進程在進行遠程通信時,彼此可以發送各種類型的數據。無論是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方需要把這個Java對象轉換為字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復為Java對象。

          Java對象轉換為字節序列的過程稱為對象的序列化

          把字節序列恢復為Java對象的過程稱為對象的反序列化


          -----------以下內容節選自《Thinking in java 3rd Edition》-------------
               

              利用對象序列化可以實現“輕量級持久化”(lightweight persistence)。“持久化”意味著一個對象的生存周期并不取決于程序是否正在執行;它可以生存于程序的調用之間。通過將一個序列化對象寫入磁盤,然后在重新調用時恢復該對象,就能夠實現持久化的效果。之所以稱其為“輕量級”,是因為不能用某種“persistent”(持久)關鍵字來簡單地定義一個對象,并讓系統自動維護其他細節問題(盡管將來有可能實現)。相反,對象必須在程序中顯式地序列化和重組。如果需要一個更嚴格的持久化機制,可以考慮使用Java數據對象(JDO)或者像Hibernate之類的工具

              對象序列化的概念加入到語言中是為了提供對兩種主要特性的支持:

              ·Java的“遠程方法調用”(RMIRemote Method Invocation)使存活于其他計算機上的對象使用起來就像是存活于本機上一樣。當向遠程對象發送消息時,需要通過對象序列化來傳輸參數和返回值。

          ·對Java Beans來說對象序列化也是必需的。使用一個Bean時,一般情況下是在設計階段對它的狀態信息進行配置。這種狀態信息必須保存下來,并在程序啟動以后,進行恢復;具體工作由對象序列化完成。

          -----------------------------------------------


              對象的序列化主要有兩種用途:

          1) 把對象的字節序列永久地保存到硬盤上,通常存放在一個文件中;

          2) 在網絡上傳送對象的字節序列。

          一.             JDK類庫中的序列化API

          java.io.ObjectOutputStream代表對象輸出流,它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。

          java.io.ObjectInputStream代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回。

          只有實現了SerializableExternalizable接口的類的對象才能被序列化。Externalizable接口繼承自Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行為,而僅實現Serializable接口的類可以采用默認的序列化方式

          對象序列化包括如下步驟:

          1) 創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,如文件輸出流;

          2) 通過對象輸出流的writeObject()方法寫對象。

          對象反序列化的步驟如下:

          1) 創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,如文件輸入流;

          2) 通過對象輸入流的readObject()方法讀取對象。

          下面讓我們來看一個對應的例子,類的內容如下:

          import java.io.*;

          import
           java.util.Date;

          /**

           * 對象的序列化和反序列化測試類.     

           * 
          @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>

           * 
          @version
           1.0 

           * Creation date: 2007-9-15 - 下午21:45:48

           
          */


          public class ObjectSaver {

                 
          /**

                  * 
          @param args

                  * 
          @author
           <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>

                  * Creation date: 2007-9-15 - 下午21:45:37

                  
          */


                 
          public static void main(String[] args) throws Exception {

                        ObjectOutputStream out 
          = new
           ObjectOutputStream

                               (
          new FileOutputStream("D:""objectFile.obj"
          ));

                        
          //序列化對象


                        Customer customer 
          = new Customer("阿蜜果"24);

                        out.writeObject(
          "你好!"
          );

                        out.writeObject(
          new
           Date());

                        out.writeObject(customer);

                        out.writeInt(
          123); //寫入基本類型數據


                        out.close();

                        
          //反序列化對象

                        ObjectInputStream in 
          = new ObjectInputStream

                               (
          new FileInputStream("D:""objectFile.obj"
          ));

                        System.out.println(
          "obj1=" +
           (String) in.readObject());

                        System.out.println(
          "obj2=" +
           (Date) in.readObject());

                        Customer obj3 
          =
           (Customer) in.readObject();

                        System.out.println(
          "obj3=" +
           obj3);

                        
          int obj4 =
           in.readInt();

                        System.out.println(
          "obj4=" +
           obj4);

                        in.close();

                 }


          }


          class Customer implements Serializable {

                 
          private
           String name;

                 
          private int
           age;

                 
          public Customer(String name, int age) 
          {

                        
          this.name =
           name;

                        
          this.age =
           age;

                 }


                 
          public String toString() {

                        
          return "name=" + name + ", age=" +
           age;

                 }


          }

           輸出結果如下:

          obj1=你好!

          obj2=Sat Sep 15 22:02:21 CST 2007

          obj3=name=阿蜜果, age=24

          obj4=123

              因此例比較簡單,在此不再詳述。


          二    實現Serializable接口

          ObjectOutputStream只能對Serializable接口的類的對象進行序列化。默認情況下,ObjectOutputStream按照默認方式序列化,這種序列化方式僅僅對對象的非transient的實例變量進行序列化,而不會序列化對象的transient的實例變量,也不會序列化靜態變量

              當
          ObjectIntputStream按照默認方式反序列化時,具有如下特點:

          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()方法中,可以先調用ObjectOutputStreamdefaultWriteObject()方法,使得對象輸出流先執行默認的序列化操作。同理可得出反序列化的情況,不過這次是defaultReadObject()方法。

          有些對象中包含一些敏感信息,這些信息不宜對外公開。如果按照默認方式對它們序列化,那么它們的序列化數據在網絡上傳輸時,可能會被不法份子竊取。對于這類信息,可以對它們進行加密后再序列化,在反序列化時則需要解密,再恢復為原來的信息——這是transient關鍵字的第一個用途,屏蔽敏感信息。

          transient的第二個功能是在用途時,將某些無需序列化的成員變量設為transient類型,將節省空間和時間,提高序列化的性能。

              transient的第三個用途是如果類的內部有某個非序列化的對象引用,可以將其標記為transient來避免拋出NotSerializableException異常

              默認的序列化方式會序列化整個對象圖,這需要遞歸遍歷對象圖。如果對象圖很復雜,遞歸遍歷操作需要消耗很多的空間和時間,它的內部數據結構為雙向列表。


              注意在遞歸遍歷過程中,對同一對象的引用如果出現多次,序列化過程并不會重復寫入多個,具體的做法如下:
              ·保存到磁盤的所有對象都獲得一個序列號(1、2、3等)
              ·當要保存一個對象時,先檢查該對象是否已經被保存了
              ·如果以前保存過,只需寫入“與已經保存的具有序列號x的對象相同”標記;否則,保存它的所有數據

              當需要讀回對象時,將上述過程簡單地逆轉即可。
              

          三        實現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異常。

              
          類實現externalizable時,頭寫入對象流中,然后類完全負責序列化和恢復數據成員,除了頭以外,根本沒有自動序列化。

              這里要注意了:聲明類實現Externalizable接口會有重大的安全風險。writeExternal()與readExternal()方法聲明為public,惡意類可以用這些方法讀取和寫入對象數據。如果對象包含敏感信息,則要格外小心。這包括使用安全套接或加密整個字節流。


          四    可序列化類的不同版本的序列化兼容性

              凡是實現Serializable
          接口的類都有一個表示序列化版本標識符的靜態變量:

              private static final long serialVersionUID;


              以上
          serialVersionUID的取值是Java運行時環境根據類的內部細節自動生成的。如果對類的源代碼作了修改,再重新編譯,新生成的類文件的serialVersionUID的取值有可能也會發生變化。

          類的serialVersionUID的默認值完全依賴于Java編譯器的實現,對于同一個類,用不同的Java編譯器編譯,有可能會導致不同的serialVersionUID,也有可能相同。為了提高serialVersionUID的獨立性和確定性,強烈建議在一個可序列化類中顯示的定義serialVersionUID,為它賦予明確的值。顯式地定義serialVersionUID有兩種用途:

          1              在某些場合,希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有相同的serialVersionUID

          2)              在某些場合,不希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有不同的serialVersionUID


          五    利用序列化來“克隆”對象

              要知道,序列化是對對象的一個“深拷貝”,為此我們完全可以用序列化來克隆一個對象(如果支持的話)。

              要克隆序列化對象,簡單地將該對象序列化到輸出流中去,然后再讀取回來。結果就是一個對已經存在的對象進行了深拷貝的新對象。我們不需要將該對象寫入文件中——可以使用ByteArrayOutputStream將數據保存在字節數組中。

              但是,這種方法盡管很聰明,但是比創建一個新的對象,然后拷貝或克隆數據字段的克隆方法要慢許多。

              例:
              
          /**
            A class whose clone method uses serialization
          */


          class SerialCloneable implements Cloneable, Serializable
          {
              
          try 
          {
                  
          // save the object to a byte array

                  ByteArrayOutputStream bout = new ByteArrayOutputStream();
                  ObjectOutputStream out 
          = new
           ObjectOutputStream(bout);
                  out.writeObject(
          this
          );
                  out.close();

                  
          // read a clone of the object from the byte array

                  ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray);
                  ObjectInputStream in 
          = new
           ObjectInputStream(bin);
                  Object ret 
          =
           in.readObject();
                  in.close();

                  ret.close();
              }

              
          catch(Exception e) {
                  
          return null
          ;
              }

          }


          /**
            Now the class Employee is able to clone itself using serialization
          */

          class Employee extends SerialCloneable {
              
          // 

          }


          六    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接口
          posted on 2008-10-29 21:19 This is Wing 閱讀(4896) 評論(0)  編輯  收藏 所屬分類: Java基礎
           
          Copyright © This is Wing Powered by: 博客園 模板提供:滬江博客
          主站蜘蛛池模板: 东乡族自治县| 东乡县| 马鞍山市| 洛川县| 堆龙德庆县| 华坪县| 荔波县| 皮山县| 城固县| 陇西县| 尼木县| 太仆寺旗| 福州市| 铁岭市| 赤城县| 广河县| 成都市| 星子县| 涟水县| 揭东县| 图片| 阆中市| 本溪市| 广东省| 贺兰县| 河北区| 梅河口市| 承德市| 孝义市| 扬州市| 杭州市| 阿城市| 永川市| 桐柏县| 来凤县| 利川市| 石楼县| 金塔县| 平原县| 大同县| 玉溪市|