-
序列化是什么:
序列化就是將一個對象的狀態(各個屬性量)保存起來,然后在適當的時候再獲得。
序列化分為兩大部分:序列化和反序列化。序列化是這個過程的第一部分,將數據分解成字節流,以便存儲在文件中或在網絡上傳輸。反序列化就是打開字節流并重構對象。對象序列化不僅要將基本數據類型轉換成字節表示,有時還要恢復數據。恢復數據要求有恢復數據的對象實例
序列化的什么特點:
如果某個類能夠被序列化,其子類也可以被序列化。聲明為static和transient類型的成員數據不能被序列化。因為static代表類的狀態, transient代表對象的臨時數據。
public interface Serializable (API5.0)
類通過實現 java.io.Serializable 接口以啟用其序列化功能。未實現此接口的類將無法使其任何狀態序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于標識可序列化的語義。
要允許不可序列化類的子類型序列化,可以假定該子類型負責保存和還原超類型的公用 (public)、受保護的 (protected) 和(如果可訪問)包 (package) 字段的狀態。僅在子類型擴展的類有一個可訪問的無參數構造方法來初始化該類的狀態時,才可以假定子類型有此責任。如果不是這種情況,則聲明一個類為可序列化類是錯誤的。該錯誤將在運行時檢測到。
在反序列化過程中,將使用該類的公用或受保護的無參數構造方法初始化不可序列化類的字段。可序列化的子類必須能夠訪問無參數的構造方法。可序列化子類的字段將從該流中還原。
當遍歷一個圖形時,可能會遇到不支持可序列化接口的對象。在此情況下,將拋出 NotSerializableException,并將標識不可序列化對象的類。
在序列化和反序列化過程中需要特殊處理的類必須使用下列準確簽名來實現特殊方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
writeObject 方法負責寫入特定類的對象的狀態,以便相應的 readObject 方法可以還原它。通過調用 out.defaultWriteObject 可以調用保存 Object 的字段的默認機制。該方法本身不需要涉及屬于其超類或子類的狀態。狀態是通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數據類型的方法將各個字段寫入 ObjectOutputStream 來保存的。
readObject 方法負責從流中讀取并還原類字段。它可以調用 in.defaultReadObject 來調用默認機制,以還原對象的非靜態和非瞬態字段。defaultReadObject 方法使用流中的信息來分配流中通過當前對象中相應命名字段保存的對象的字段。這用于處理類發展后需要添加新字段的情形。該方法本身不需要涉及屬于其超類或子類的狀態。狀態是通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數據類型的方法將各個字段寫入 ObjectOutputStream 來保存的。
將對象寫入流時需要指定要使用的替代對象的可序列化類,應使用準確的簽名來實現此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法將由序列化調用,前提是如果此方法存在,而且它可以通過被序列化對象的類中定義的一個方法訪問。因此,該方法可以擁有私有 (private)、受保護的 (protected) 和包私有 (package-private) 訪問。子類對此方法的訪問遵循 java 訪問規則。
在從流中讀取類的一個實例時需要指定替代的類應使用的準確簽名來實現此特殊方法。
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循與 writeReplace 相同的調用規則和訪問規則。
序列化運行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯,該序列號在反序列化過程中用于驗證序列化對象的發送者和接收者是否為該對象加載了與序列化兼容的類。如果接收者加載的該對象的類的 serialVersionUID 與對應的發送者的類的版本號不同,則反序列化將會導致 InvalidClassException
。可序列化類可以通過聲明名為 "serialVersionUID"
的字段(該字段必須是靜態 (static)、最終 (final) 的 long
型字段)顯式聲明其自己的 serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基于該類的各個方面計算該類的默認 serialVersionUID 值,如“Java(TM) 對象序列化規范”中所述。不過,強烈建議 所有可序列化類都顯式聲明 serialVersionUID 值,原因計算默認的 serialVersionUID 對類的詳細信息具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException
。因此,為保證 serialVersionUID 值跨不同 java 編譯器實現的一致性,序列化類必須聲明一個明確的 serialVersionUID 值。還強烈建議使用 private
修改器顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應用于立即聲明類 -- serialVersionUID 字段作為繼承成員沒有用處
實用意義:
一:對象序列化可以實現分布式對象。主要應用例如:RMI要利用對象序列化運行遠程主機上的服務,就像在本地機上運行對象時一樣。
二:java對象序列化不僅保留一個對象的數據,而且遞歸保存對象引用的每個對象的數據。可以將整個對象層次寫入字節流中,可以保存在文件中或在網絡連接上傳遞。利用對象序列化可以進行對象的“深復制”,即復制對象本身及引用的對象本身。序列化一個對象可能得到整個對象序列。
EP:
import java.io.Serializable;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
class MeTree implements Serializable {
private static final long serialVersionUID = 42L;
public MeTree left;
public MeTree right;
public int id;
public int level;
private static int count = 0 ;
public MeTree( int depth) {
id = count ++ ;
level = depth;
if (depth > 0 ) {
left = new MeTree(depth - 1 );
right = new MeTree(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( " MeTree.tmp " );
/**/ /* 創建輸出流 */
ObjectOutputStream p = new ObjectOutputStream(ostream);
/**/ /* 創建一個二層的樹。 */
MeTree base = new MeTree( 2 );
p.writeObject(base); // 將樹寫入流中。
p.writeObject( " LiLy is 惠止南國 " );
p.flush();
ostream.close(); // 關閉文件。
/**/ /* 打開文件并設置成從中讀取對象。 */
FileInputStream istream = new FileInputStream( " MeTree.tmp " );
ObjectInputStream q = new ObjectInputStream(istream);
/**/ /* 讀取樹對象,以及所有子樹 */
MeTree new_MeTree = (MeTree)q.readObject();
new_MeTree.print( 2 ); // 打印出樹形結構的最上面 2級
String name = (String)q.readObject();
System.out.println( " \n " + name);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
可以看到,在序列化的時候,writeObject與readObject之間的先后順序。readObject將最先write的object read出來。用數據結構的術語來講就姑且稱之為先進先出吧!
在序列化時,有幾點要注意的:
1:當一個對象被序列化時,只保存對象的非靜態成員變量,不能保存任何的成員方法和靜態的成員變量。
2:如果一個對象的成員變量是一個對象,那么這個對象的數據成員也會被保存。
3:如果一個可序列化的對象包含對某個不可序列化的對象的引用,那么整個序列化操作將會失敗,并且會拋出一個NotSerializableException。我們可以將這個引用標記為transient,那么對象仍然可以序列化
還有我們對某個對象進行序列化時候,往往對整個對象全部序列化了,比如說類里有些數據比較敏感,不希望序列化,一個方法可以用transient來標識,另一個方法我們可以在類里重寫
可以通過指定關鍵transient使對象中的某個數據元素不被還原,這種方式常用于安全上的保護。比如對象中保存的密碼。
//**
transient 只能用在類的成員變量上,不能用在方法里.
transient 變量不能是final和static的
transient(臨時)關鍵字
控制序列化過程時,可能有一個特定的子對象不愿讓Java的序列化機制自動保存與恢復。一般地,若那個子對象包含了不想序列化的敏感信息(如密碼),就會面臨這種情況。即使那種信息在對象中具有“private”(私有)屬性,但一旦經序列化處理,人們就可以通過讀取一個文件,或者攔截網絡傳輸得到它。
為防止對象的敏感部分被序列化,一個辦法是將自己的類實現為Externalizable,就象前面展示的那樣。這樣一來,沒有任何東西可以自動序列化,只能在writeExternal()明確序列化那些需要的部分。
然而,若操作的是一個Serializable對象,所有序列化操作都會自動進行。為解決這個問題,可以用transient(臨時)逐個字段地關閉序列化,它的意思是“不要麻煩你(指自動機制)保存或恢復它了——我會自己處理的”。
例如,假設一個Login對象包含了與一個特定的登錄會話有關的信息。校驗登錄的合法性時,一般都想將數據保存下來,但不包括密碼。為做到這一點,最簡單的辦法是實現Serializable,并將password字段設為transient。
password被設為transient,所以不會自動保存到磁盤;另外,自動序列化機制也不會作恢復它的嘗試。
一旦對象恢復成原來的樣子,password字段就會變成null。注意必須用toString()檢查password是否為null,因為若用過載的“+”運算符來裝配一個String對象,而且那個運算符遇到一個null句柄,就會造成一個名為NullPointerException的違例(新版Java可能會提供避免這個問題的代碼)。
我們也發現date字段被保存到磁盤,并從磁盤恢復,沒有重新生成。
由于Externalizable對象默認時不保存它的任何字段,所以transient關鍵字只能伴隨Serializable使用。
**// 還有我們對某個對象進行序列化時候,往往對整個對象全部序列化了,比如說類里有些數據比較敏感,不希望序列化,一個方法可以用transient來標識,另一個方法我們可以在類里重寫
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的機制,不再需要Serializable6、設計用來被繼承的類時,盡量不實現Serializable,用來被繼承的interface也不要 繼承Serializable。但是如果父類不實現Serializable接口,子類很難實現它,特別是 對于父類沒有可以訪問的不含參數的構造函數的時候。所以,一旦你決定不實現 Serializable接口并且類被用來繼承的時候記得提供一個無參數的構造函數 7、內部類還是不要實現Serializable好了,除非是static的,(偶也覺得內部類不適合 用來干這類活的) 8、使用一個自定義的序列化方法
看看下面這個保存一個雙向鏈表的例子:
publicclass StringList implementsSerializable
{
?privateint size = 0;
?private Entry head = null;
?
?privatestaticclass Entry implements Serializable
?{
? String data;
? Entry next;
? Entry previous;
?}
?...//Remainder ommitted
}
這樣會導致鏈表的每個元素以及元素之間的關系(雙向鏈表之間的連接)
都保存下來,更好的方法是提供一個自定義的序列化如下: //String List with a resonable custom serialized form
class StringList implementsSerializable
{
? privatetransientint size = 0;?????? //!transient
? privatetransient Entry head = null;? //!transient
?
? //no longer serializable!
? privatestaticclass Entry
? {
??? String data;
??? Entry next;
??? Entry previous;
? }
?
? //Appends the specified string to the list
? publicvoid add(String s) {/*...*/};
?
? /**
?? * Serialize thisStringList
instance
?? * @author yuchifang
?? * @serialData The size of the list (the number of strings
* it contains) is emitted(int), in the proper sequence
?? */
? privatevoid 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);
? }
?
? privatevoid 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
完全定制序列化過程:
如果一個類要完全負責自己的序列化,則實現Externalizable接口而不是Serializable接口。Externalizable接口定義包括兩個方法writeExternal()與readExternal()。利用這些方法可以控制對象數據成員如何寫入字節流.類實現Externalizable時,頭寫入對象流中,然后類完全負責序列化和恢復數據成員,除了頭以外,根本沒有自動序列化。這里要注意了。聲明類實現Externalizable接口會有重大的安全風險。writeExternal()與readExternal()方法聲明為public,惡意類可以用這些方法讀取和寫入對象數據。如果對象包含敏感信息,則要格外小心。這包括使用安全套接或加密整個字節流。到此為至,我們學習了序列化的基礎部分知識。關于序
列化的高級教程,以后再述。