天天看點

Serializable java序列化

 Bean Serializable Interface 的接口讓BEAN可以串行化,将其變成一個可儲存為以後使用的二進制流。當一個BEAN被系列化到磁盤上或者其他任何地方,其狀态被儲存起來,其中的屬性值也不會改變。在BEAN的規範中,JSP并沒有要求BEAN實作Serializable接口。但是,如果您希望自己控制您所建立的元件的serialization程序,或者您想serialize并不是标準元件擴充的元件,您必須了解serialization and deserialization的細節。

有幾個原因你會把BEAN冷藏起來以備後用。有些伺服器通過将所有的SESSION 資料(包括BEAN)寫入磁盤來支援任意長的SESSION生命期,即使伺服器停機也不會丢失。當伺服器重新啟動後,串行化的資料被恢複。同樣的理由,在重負載的站點上支援伺服器分簇的環境中,許多伺服器通過串行化來複制SESSION。如果你的BEAN不支援串行化,伺服器就不能正确地儲存和傳輸類。

通過同樣的政策,你可以選擇将BEAN儲存在磁盤上或者資料庫中,以備後用。例如,也許可以将客戶的購物車實作為一個BEAN,在通路期間将其儲存在資料庫中。

如果BEAN需要特殊的複雜的初始設定,可以将BEAN設定好後串行化儲存在磁盤上。這個BEAN的“快照”可以用在任何需要的地方,包括在$#@60;jsp:useBean$#@62;中用beanName屬性的調用。

$#@60;jsp:useBean$#@62;标簽中的beanName屬性,用來執行個體化一個串行化的BEAN,而不是用來從一個類建立一個全新的執行個體。如果BEAN還沒有建立,beanName屬性傳給java.beans.Bean.instantiate()方法,由類裝載器對類進行執行個體化。它首先假定存在一個串行化的BEAN(帶有擴充名.ser),然後會将其激活。如果這個操作失敗,它就會執行個體化一個新的執行個體。

下面簡單介紹一下這個接口:

對象能包含其它的對象,而這其它的對象又可以包含另外的對象。JAVA serialization能夠自動的處理嵌套的對象。對于一個對象的簡單的域,writeObject()直接将值寫入流。而,當遇到一個對象域時,writeObject()被再次調用,如果這個對象内嵌另一個對象,那麼,writeObject() 又被調用,直到對象能被直接寫入流為止。程式員所需要做的是将對象傳入ObjectOutputStream 的writeObject() 方法,剩下的将又系統自動完成。下面的例子建立了一個調用mine對象的PersonalData對象。代碼實作的是将一個串和mine 對象輸出到一個流,并存入一個檔案:

public class PersonalData implements Serializable {

public int id

public int yearOfBirth;

public float yearlySalary;

}

PersonalData mine = new PersonalData(101, 1956, 46500.00);

FileOutputStream outstream = new FileOutputStream("PersonalData.ser");

ObjectOutputStream out = new ObjectOutputStream(outstream);

out.writeObject("My personal data"); //将一個串寫入流

out.writeObject(mine); //将這個對象寫入流

out.close(); // 清空并關閉流

...

一個FileOutputStream對象被建立且傳到一個ObjectOutputStream。當out.writeObject() 被調用,這個串和mine 對象被objects are serializ順序加入一個存入檔案PersonalData.ser的位元組對列。

您應該注意上述類是實作的java.io.Serializable接口。因為它并未指定要實作的方法,是以Serializable被稱為"tagging interface" ,但是它僅僅"tags"它自己的對象是一個特殊的類型。任一個您希望serialize的對象都應該實作這個接口。這是必須的。否則,用到流技術時将根本不工作。例如,如果您試着去serialize 一個沒有實作這個接口的對象,一個 NotSerializableException将産生。

類通過實作 java.io.Serializable 接口以啟用其序列化功能。未實作此接口的類将無法使其任何狀态序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于辨別可序列化的語義。

Java的"對象序列化"能讓你将一個實作了Serializable接口的對象轉換成一組byte,這樣日後要用這個對象時候,你就能把這些byte資料恢複出來,并據此重新建構那個對象了。

要想序列化對象,你必須先建立一個OutputStream,然後把它嵌進ObjectOutputStream。這時,你就能用writeObject( )方法把對象寫入OutputStream了。

writeObject 方法負責寫入特定類的對象的狀态,以便相應的 readObject 方法可以還原它。通過調用 out.defaultWriteObject 可以調用儲存 Object 的字段的預設機制。該方法本身不需要涉及屬于其超類或子類的狀态。狀态是通過使用 writeObject 方法或使用 DataOutput 支援的用于基本資料類型的方法将各個字段寫入 ObjectOutputStream 來儲存的。

  讀的時候,你得把InputStream嵌到ObjectInputStream裡面,然後再調用readObject( )方法。不過這樣讀出來的,隻是一個Object的reference,是以在用之前,還得先下傳。readObject 方法負責從流中讀取并還原類字段。它可以調用 in.defaultReadObject 來調用預設機制,以還原對象的非靜态和非瞬态字段。

defaultReadObject 方法使用流中的資訊來配置設定流中通過目前對象中相應命名字段儲存的對象的字段。這用于處理類發展後需要添加新字段的情形。該方法本身不需要涉及屬于其超類或子類的狀态。狀态是通過使用 writeObject 方法或使用 DataOutput 支援的用于基本資料類型的方法将各個字段寫入 ObjectOutputStream 來儲存的。

看一個列子:

 import  java.io. * ; 

 class  tree  implements  java.io.Serializable  { 

     public  tree left; 

     public  tree right; 

     public   int  id; 

     public   int  level; 

     private   static   int  count  =   0 ; 

     public  tree( int  depth)  { 

        id  =  count ++ ; 

        level  =  depth; 

         if  (depth  >   0 )  { 

            left  =   new  tree(depth - 1 ); 

            right  =   new  tree(depth - 1 ); 

        }  

    }  

     public   void  print( int  levels)  { 

         for  ( int  i  =   0 ; i  <  level; i ++ ) 

            System.out.print( "    " ); 

        System.out.println( " node  "   +  id); 

         if  (level  <=  levels  &&  left  !=   null ) 

            left.print(levels); 

         if  (level  <=  levels  &&  right  !=   null ) 

            right.print(levels); 

     public   static   void  main (String argv[])  { 

         try   { 

             /*  建立一個檔案寫入序列化樹。  */  

            FileOutputStream ostream  =   new  FileOutputStream( " tree.tmp " ); 

             /*  建立輸出流  */  

            ObjectOutputStream p  =   new  ObjectOutputStream(ostream); 

             /*  建立一個二層的樹。  */  

            tree base  =   new  tree( 2 ); 

            p.writeObject(base);  //  将樹寫入流中。  

             p.writeObject( " LiLy is 惠止南國 " );

            p.flush(); 

            ostream.close();     //  關閉檔案。  

              /*  打開檔案并設定成從中讀取對象。  */  

            FileInputStream istream  =   new  FileInputStream( " tree.tmp " ); 

            ObjectInputStream q  =   new  ObjectInputStream(istream); 

             /*  讀取樹對象,以及所有子樹  */  

            tree new_tree  =  (tree)q.readObject(); 

            new_tree.print( 2 );   //  列印出樹形結構的最上面 2級  

             String name  =  (String)q.readObject();

            System.out.println( " /n " + name);

        }   catch  (Exception ex)  { 

            ex.printStackTrace(); 

}  

  最後結果如下:

    node 0

  node 1

node 2

node 3

  node 4

node 5

node 6

LiLy is 惠止南國 

  可以看到,在序列化的時候,writeObject與readObject之間的先後順序。readObject将最先write的object read出來。用資料結構的術語來講就姑且稱之為先進先出吧!

在序列化時,有幾點要注意的:

1:當一個對象被序列化時,隻儲存對象的非靜态成員變量,不能儲存任何的成員方法和靜态的成員變量。

2:如果一個對象的成員變量是一個對象,那麼這個對象的資料成員也會被儲存。

3:如果一個可序列化的對象包含對某個不可序列化的對象的引用,那麼整個序列化操作将會失敗,并且會抛出一個NotSerializableException。我們可以将這個引用标記為transient,那麼對象仍然可以序列化

還有我們對某個對象進行序列化時候,往往對整個對象全部序列化了,比如說類裡有些資料比較敏感,不希望序列化,一個方法可以用transient來辨別,另一個方法我們可以在類裡重寫

 private   void  readObject(java.io.ObjectInputStream stream)

      throws  IOException, ClassNotFoundException;

  private   void  writeObject(java.io.ObjectOutputStream stream)

      throws  IOException

  這二個方法!

  示例:

 import  java.io. * ;

 class  ObjectSerialTest

 {

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

     {

        Employee e1 = new  Employee( " zhangsan " , 25 , 3000.50 );

        Employee e2 = new  Employee( " lisi " , 24 , 3200.40 );

        Employee e3 = new  Employee( " wangwu " , 27 , 3800.55 );

        FileOutputStream fos = new  FileOutputStream( " employee.txt " );

        ObjectOutputStream oos = new  ObjectOutputStream(fos);

        oos.writeObject(e1);

        oos.writeObject(e2);

        oos.writeObject(e3);

        oos.close();

        FileInputStream fis = new  FileInputStream( " employee.txt " );

        ObjectInputStream ois = new  ObjectInputStream(fis);

        Employee e;

         for ( int  i = 0 ;i < 3 ;i ++ )

         {

            e = (Employee)ois.readObject();

            System.out.println(e.name + " : " + e.age + " : " + e.salary);

        } 

        ois.close();

    } 

 class  Employee  implements  Serializable

    String name;

     int  age;

     double  salary;

     transient  Thread t = new  Thread();

     public  Employee(String name, int  age, double  salary)

         this .name = name;

         this .age = age;

         this .salary = salary;

     private   void  writeObject(java.io.ObjectOutputStream oos)  throws  IOException

        oos.writeInt(age);

        oos.writeUTF(name);

        System.out.println( " Write Object " );

     private   void  readObject(java.io.ObjectInputStream ois)  throws  IOException

        age = ois.readInt();

        name = ois.readUTF();

        System.out.println( " Read Object " );

  --(add on 2006/6/28) 

參考資料:JDK1.5 API DOC  孫鑫老師資料  

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、内部類還是不要實作Serializable好了,除非是static的,(偶也覺得内部類不适合

用來幹這類活的)

8、使用一個自定義的序列化方法

  看看下面這個儲存一個雙向連結清單的例子:

public class StringList implements Serializable

{

?private int size = 0;

?private Entry head = null;

?

?private static class Entry implements Serializable

?{

? String data;

? Entry next;

? Entry previous;

?}

?...//Remainder ommitted

這樣會導緻連結清單的每個元素以及元素之間的關系(雙向連結清單之間的連接配接)

都儲存下來,更好的方法是提供一個自定義的序列化如下:

//String List with a resonable custom serialized form

class StringList implements Serializable

? private transient int size = 0;?????? //!transient

? private transient Entry head = null;? //!transient

? //no longer serializable!

? private static class Entry

? {

??? String data;

??? Entry next;

??? Entry previous;

? }

? //Appends the specified string to the list

? public void add(String s) {/*...*/};

? /**

?? * Serialize this <code>StringList</code> instance 

?? * @author yuchifang

?? * @serialData The size of the list (the number of strings

   * it contains) is emitted(int), in the proper sequence

?? */

? private void writeObject(ObjectOutputStream s)

               throws IOException

??? s.defaultWriteObject();

??? s.writeInt(size);

??? //Write out all elements in the proper order

??? for (Entry e = head; e != null; e = e.next)

????? s.writeObject(e.data);

? private void readObject(ObjectInputStream s)

               throws IOException, ClassNotFoundException

??? int numElements = s.readInt();

??? 

??? //Read in all elements andd insert them in list

??? for (int i = 0; i &lt; numElements; i++)

????? add((String)s.readObject());

? //...remainder omitted

9、不管你選擇什麼序列化形式,聲明一個顯式的UID:

private static final long serialVersionUID = randomLongValue;

10、不需要序列化的東西使用transient注掉它吧,别什麼都留着

11、writeObject/readObject重載以完成更好的序列化

readResolve 與 writeReplace重載以完成更好的維護invariant controllers

MarshalByRefObject和Serializable

最近在看web sevice 方面的東西,順便看了下序列化,懂了不少啊 :

從MarshalByRefObject派生的類和有[Serializable]的類都可以跨越應用程式域作為參數傳遞。

從MarshalByRefObject派生的類按引用封送,有[Serializable]标志的類,按值封送。

如果此類即從MarshalByRefObject派生,也有[Serializable]标志也是按引用封送。

序列化有3種情況:

序列化為XML格式:

在webservice裡,寫個web method,傳個自定義類做參數,就是這種情況。系統會幫你搞定,把自定義的類轉換為預設XML格式。

序列化為2進制:

要加[Serializable]标志,可以把私有變量和公共變量都序列化。

序列化為soap格式:

需要實作ISerializable接口,定義序列化函數ISerializable.GetObjectData,和還原序列化的構造函數。

一個soap參數類的sample:

Serializable java序列化

[Serializable]

Serializable java序列化

    public class serialze:ISerializable 

Serializable java序列化

    {

Serializable java序列化

        // 序列化函數,由 SoapFormatter 在序列化過程中調用

Serializable java序列化

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext

Serializable java序列化

            ctxt)

Serializable java序列化

        {

Serializable java序列化

            // 向 SerializationInfo 對象中添加每個字段

Serializable java序列化

            info.AddValue("UserName", UserName);

Serializable java序列化

            info.AddValue("UserID",UserID);

Serializable java序列化

        }

Serializable java序列化
Serializable java序列化

        // 還原序列化構造函數,由 SoapFormatter 在還原序列化過程中調用

Serializable java序列化

        public serialze(SerializationInfo info, StreamingContext ctxt)

Serializable java序列化
Serializable java序列化

            // 從 SerializationInfo 對象中還原序列化出各個字段

Serializable java序列化

            UserName = (string)info.GetValue("UserName", typeof(string));

Serializable java序列化

            UserID = (int) info.GetValue("UserID",typeof(int));

Serializable java序列化
Serializable java序列化
Serializable java序列化

        public serialze()

Serializable java序列化

        {}

Serializable java序列化
Serializable java序列化

        public string UserName;

Serializable java序列化

        public int UserID;

Serializable java序列化

    }

是的,如果Session要存到資料庫中就必須添加Serializable标記~