為什麼XML需要序列化和反序列化
阿新 • • 發佈:2019-02-15
注意:“為避免編譯錯誤,為可序列化的類添加了無引數建構函式。”
MSDN的定義:序列化是將物件狀態轉換為可保持或可傳輸的形式的過程。序列化的補集是反序列化,後者將流轉換為物件。這兩個過程一起保證資料易於儲存和傳輸。
大家關心的是為什麼需要序列化,用傳統的方法也能實現這種需求嗎,它存在的價值是什麼,低層的原理、實質、基因的區別是什麼?這也是我的疑問,通過在網上搜集,找到了較滿意的答案,分享給大家。
答案一:序列化是用來通訊的,服務端把資料序列化,傳送到客戶端,客戶端把接收到的資料反序列化後對資料進行操作,完成後再序列化傳送到服務端,服務端再反序列化資料後對資料進行操作。說白了,資料需要序列化以後才能在服務端和客戶端之間傳輸。這個服務端和客戶端的概念是廣義的,可以在網路上,也可以在同一臺機器的不同程序中,甚至在同一個程序中進行通訊。在傳統程式設計中,物件是通過呼叫棧間接的與客戶端互動,但在面向服務的程式設計中,客戶端永遠都不會直接呼叫例項。不知道說的明不明白。
好吧,我說的確實不夠明白,你問的是為什麼需要序列化,我只是說了序列化的一個應用。那我就來說說序列化的好處吧。不序列化也可以傳輸,但是無法跨平臺,安全性也無法保障。我說的是面向服務程式設計中的作用,在傳統程式設計中,你在表示層例項化一個業務物件,然後呼叫業務物件中的方法,你想過為什麼能這樣呼叫嗎?這樣做耦合度太高,很不好。如果序列化以後通過特定的協議傳輸資料就不一樣了,表示層通過代理或通道向服務層傳送特定的資料格式,這個資料就是序列化以後的,比如XML,服務端接收到以後要進行反序列化,生成服務端可識別的資料格式,比如一個類,然後對資料進行操作,再序列化傳送到客戶端,客戶端再反序列化。這樣客戶端可以使用和服務端完全不同的開發平臺,只要它能夠對xml資料進行反序列化,而xml是具有工業標準的資料格式,基本各平臺都支援。這也適用於在程序間通訊。如果在程序內通訊,也可以做到更高的安全性,物件不再通過呼叫棧互動,而是通過代理或通道。
答案二:這個更進一步的解釋了其真正的價值。
簡單來說序列化就是一種用來處理物件流的機制,所謂物件流也就是將物件的內容進行流化,流的概念這裡不用多說(就是I/O),我們可以對流化後的物件進行讀寫操作,也可將流化後的物件傳輸於網路之間(注:要想將物件傳輸於網路必須進行流化)!在對物件流進行讀寫操作時會引發一些問題,而序列化機制正是用來解決這些問題的!問題的引出:
如上所述,讀寫物件會有什麼問題呢?比如:我要將物件寫入一個磁碟檔案而後再將其讀出來會有什麼問題嗎?別急,其中一個最大的問題就是物件引用!舉個例子來說:假如我有兩個類,分別是A和B,B類中含有一個指向A類物件的引用,現在我們對兩個類進行例項化{ A a = new A(); B b = new B(); },這時在記憶體中實際上分配了兩個空間,一個儲存物件a,一個儲存物件b,接下來我們想將它們寫入到磁碟的一個檔案中去,就在寫入檔案時出現了問題!因為物件b包含對物件a的引用,所以系統會自動的將a的資料複製一份到b中,這樣的話當我們從檔案中恢復物件時(也就是重新載入到記憶體中)時,記憶體分配了三個空間,而物件a同時在記憶體中存在兩份,想一想後果吧,如果我想修改物件a的資料的話,那不是還要搜尋它的每一份拷貝來達到物件資料的一致性,這不是我們所希望的!
以下序列化機制的解決方案:
1.儲存到磁碟的所有物件都獲得一個序列號(1, 2, 3等等)
2.當要儲存一個物件時,先檢查該物件是否被儲存了。
3.如果以前儲存過,只需寫入"與已經儲存的具有序列號x的物件相同"的標記,否則,儲存該物件
通過以上的步驟序列化機制解決了物件引用的問題!
序列化的實現
將需要被序列化的類實現Serializable介面,該介面沒有需要實現的方法,implements Serializable只是為了標註該物件是可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個 ObjectOutputStream(物件流)物件,接著,使用ObjectOutputStream物件的writeObject(Object obj)方法就可以將引數為obj的物件寫出(即儲存其狀態),要恢復的話則用輸入流。
在序列化的過程中,有些資料欄位我們不想將其序列化,對於此類欄位我們只需要在定義時給它加上transient關鍵字即可,對於transient欄位序列化機制會跳過不會將其寫入檔案,當然也不可被恢復。但有時我們想將某一欄位序列化,但它在SDK中的定義卻是不可序列化的型別,這樣的話我們也必須把他標註為transient,可是不能寫入又怎麼恢復呢?好在序列化機制為包含這種特殊問題的類提供瞭如下的方法定義:
private void readObject(ObjectInputStream in) throws
IOException, ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws
IOException;
(注:這些方法定義時必須是私有的,因為不需要你顯示呼叫,序列化機制會自動呼叫的)
使用以上方法我們可以手動對那些你又想序列化又不可以被序列化的資料欄位進行寫出和讀入操作。
下面是一個典型的例子,java.awt.geom包中的Point2D.Double類就是不可序列化的,因為該類沒有實現Serializable介面,在我的例子中將把它當作LabeledPoint類中的一個數據欄位,並演示如何將其序列化!
import java.io.*;
import java.awt.geom.*;
public class TransientTest
{
public static void main(String[] args)
{
LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);
try
{
System.out.println(label);// 寫入前
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("Label.txt"));
out.writeObject(label);
out.close();
System.out.println(label);// 寫入後
ObjectInputStream in = new ObjectInputStream(new
FileInputStream("Label.txt"));
LabeledPoint label1 = (LabeledPoint) in.readObject();
in.close();
System.out.println(label1);// 讀出並加1.0後
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class LabeledPoint implements Serializable
{
public LabeledPoint(String str, double x, double y)
{
label = str;
point = new Point2D.Double(x, y);
}
private void writeObject(ObjectOutputStream out) throws IOException
{
out.defaultWriteObject();
out.writeDouble(point.getX());
out.writeDouble(point.getY());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
in.defaultReadObject();
double x = in.readDouble() + 1.0;
double y = in.readDouble() + 1.0;
point = new Point2D.Double(x, y);
}
public String toString()
{
return getClass().getName()+ "[label = " + label+ ", point.getX() = " + point.getX()+ ", point.getY() = " + point.getY()+ "]";
}
private String label;
transient private Point2D.Double point;
}轉自: http://blog.sina.com.cn/s/blog_5b142cae0100clon.html
總結:物件序列化,就是保證多處需要使用的物件,如果是同一個,就只例項化一次,這樣,效率高,如果物件發生了變化,只要序列化的物件流發生對應的變法即可。
什麼情況下使用:
有兩個最重要的原因促使對序列化的使用:一個原因是將物件的狀態保持在儲存媒體中,以便可以在以後重新建立精確的副本;另一個原因是通過值將物件從一個應用程式域傳送到另一個應用程式域中。