Serializable java序列化

langgufu314發表於2012-04-13

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種情況:

  1. 序列化為XML格式:
    在webservice裡,寫個web method,傳個自定義類做引數,就是這種情況。系統會幫你搞定,把自定義的類轉換為預設XML格式。
  2. 序列化為2進位制:
    要加[Serializable]標誌,可以把私有變數和公共變數都序列化。
  3. 序列化為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標記~

相關文章