序列化是什麼:
序列化就是将一個對象的狀态(各個屬性量)儲存起來,然後在适當的時候再獲得。
序列化分為兩大部分:序列化和反序列化。序列化是這個過程的第一部分,将資料分解成位元組流,以便存儲在檔案中或在網絡上傳輸。反序列化就是打開位元組流并重構對象。對象序列化不僅要将基本資料類型轉換成位元組表示,有時還要恢複資料。
恢複資料要求有恢複資料的對象執行個體
序列化的什麼特點:
如果某個類能夠被序列化,其子類也可以被序列化。聲明為static和transient類型的成員資料不能被序列化。因為static代表類的狀态, transient代表對象的臨時資料。
public interface Serializable (API5.0)
類通過實作 java.io.Serializable 接口以啟用其序列化功能。未實作此接口的類将無法使其任何狀态序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于辨別可序列化的語義。 要允許不可序列化類的子類型序列化,可以假定該子類型負責儲存和還原超類型的公用 (public)、受保護的 (protected) 和(如果可通路)包 (package) 字段的狀态。僅在子類型擴充的類有一個可通路的無參數構造方法來初始化該類的狀态時,才可以假定子類型有此責任。如果不是這種情況,則聲明一個類為可序列化類是錯誤的。該錯誤将在運作時檢測到。
在反序列化過程中,将使用該類的公用或受保護的無參數構造方法初始化不可序列化類的字段。可序列化的子類必須能夠通路無參數的構造方法。可序列化子類的字段将從該流中還原。
當周遊一個圖形時,可能會遇到不支援可序列化接口的對象。在此情況下,将抛出NotSerializableException,并将辨別不可序列化對象的類。
在序列化和反序列化過程中需要特殊處理的類必須使用下列準确簽名來實作特殊方法:
private void writeObject(java.io.ObjectOutputStream out)throws IOExceptionprivate 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 this
StringList
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重載以完成更好的序列化BR>readResolve 與 writeReplace重載以完成更好的維護invariant controllers
完全定制序列化過程:
如果一個類要完全負責自己的序列化,則實作Externalizable接口而不是Serializable接口。
Externalizable接口定義包括兩個方法writeExternal()與readExternal()。利用這些方法可以控制對象資料成員
如何寫入位元組流.類實作Externalizable時,頭寫入對象流中,然後類完全負責序列化和恢複資料成員,.
除了頭以外,根本沒有自動序列化。這裡要注意了。聲明類實作Externalizable接口會有重大的安全風險。
writeExternal()與readExternal()方法聲明為public,惡意類可以用這些方法讀取和寫入對象資料。如果對象包含敏感資訊,則要格外小心。這包括使用安全套接或加密整個位元組流。
到此為至,我們學習了序列化的基礎部分知識。關于序
列化的進階教程,以後再述.
1.Serializable
隻有一個實作Serializable接口的對象可以被序列化工具存儲和恢複。Serializable接口沒有定義任何成員。它隻用來表示一個類可以被序列化。如果一個類可以序列化,它的所有子類都可以序列化。
聲明成transient的變量不被序列化工具存儲。同樣,static變量也不被存儲。
private void serializeSessionObject(HttpSession session, File targetFile, User user) throws IOException {
targetFile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(targetFile);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
oos.flush();
oos.close();
session.setAttribute(USER_OBJECT_KEY, user);
session.setAttribute(USER_SERIALIZE_FILE_KEY, targetFile.getAbsolutePath());
}
private void unserializeSessionObject(HttpSession session, File sourceFile) throws IOException {
FileInputStream fis = new FileInputStream(sourceFile);
ObjectInputStream ois = new ObjectInputStream(fis);
User user = null;
try{
user = (User)ois.readObject();
}
catch(ClassNotFoundException ex){
throw new RuntimeException("Deserialize user object occurs error.",ex);
ois.close();
session.setAttribute(USER_SERIALIZE_FILE_KEY, sourceFile.getAbsolutePath());
2.transient 關鍵字有什麼用途
java有個特點就是序列化,簡單地來說就是可以将這個類存儲在實體空間(當然還是以檔案的形式存在),那麼當你從本地還原這個檔案時,你可以将它轉換為它本身。這可以極大地友善網絡上的一些操作,但同時,因為涉及到安全問題,是以并不希望把類裡面所有的東西都能存儲(因為那樣,别人可以通過序列化知道類裡面的内容),那麼我們就可以用上transient這個關鍵字,它的意思是臨時的,即不會随類一起序列化到本地,是以當還原後,這個關鍵字定義的變量也就不再存在。
通常,我們寫的程式都要求特定資訊能持久存在或儲存到磁盤上,以供一個程式使用或用在同一個程式的另一次運作上.這種持久性可以通過幾種方式來實作,包括寫到資料庫中或是利用JAVA為對象序列化提供的支援.不管我們選用什麼方法,類執行個體的持久性都是通過儲存類的域的狀态來完成的,儲存這些狀态,以便以後可以對它們進行通路或使用它們來建立相同的執行個體.然而,有可能并不是所有的域都需要被儲存起來.當一個執行個體被持久化時,其内部的一些域卻不需要持久化,則可以用trainsient修飾符告訴編譯器指定的域不需要被持久儲存.