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 StringList 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 < 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]
public class serialze:ISerializable
{
// 序列化函數,由 SoapFormatter 在序列化過程中調用
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext
ctxt)
{
// 向 SerializationInfo 對象中添加每個字段
info.AddValue("UserName", UserName);
info.AddValue("UserID",UserID);
}

// 還原序列化構造函數,由 SoapFormatter 在還原序列化過程中調用
public serialze(SerializationInfo info, StreamingContext ctxt)
{
// 從 SerializationInfo 對象中還原序列化出各個字段
UserName = (string)info.GetValue("UserName", typeof(string));
UserID = (int) info.GetValue("UserID",typeof(int));
}

public serialze()
{}

public string UserName;
public int UserID;
}
是的,如果Session要存到數據庫中就必須添加Serializable標記~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章