首頁 >Java >java教程 >java教程之物件序列化使用基礎範例詳解

java教程之物件序列化使用基礎範例詳解

高洛峰
高洛峰原創
2017-01-18 11:25:051207瀏覽

這個過程也可以透過網路實現,可以先在Windows機器上建立一個對象,對其序列化,然後透過網路發給一台Unix機器,然後在那裡準確無誤地重新"組裝"。像RMI、Socket、JMS、EJB它們中的一種,彼此為何能夠傳遞Java對象,當然都是物件序列化機制的功勞。

Java物件序列化機制一般來講有兩種用途:

Java的JavaBeans: Bean的狀態資訊通常是在設計時配置的,Bean的狀態資訊必須被存起來,以便當程式運作時能恢復這些狀態訊息,這需要將對象的狀態保存到文件中,而後能夠透過讀入對象狀態來重新建構對象,恢復程式狀態。
RMI允許像在本機上一樣操作遠端機器上的物件;或使用套接字在網路上傳送物件的程式來說,這些都是需要實現serializaiton機制的。
我們透過讓類別實作Java.io.Serializable 介面可以將類別序列化。這個介面是一個製造者(marker)介面。也就是說,對於要實作它的類別來說,該介面不需要實作任何方法。它主要用來通知Java虛擬機器(JVM),需要將一個物件序列化。

對於這個,有幾點我們需要明確:

並非所有類別都可以序列化,在cmd下,我們輸入serialver Java.net.Socket,可以得到socket是否可序列化的信息,實際上socket是不可序列化的。
Java有很多基礎類別已經實現了serializable接口,例如string,vector等。但是比如hashtable就沒有實作serializable介面。
將物件讀出或寫入流的主要類別有兩個: ObjectOutputStream與ObjectInputStream .ObjectOutputStream 提供用來將物件寫入輸出流的writeObject方法, ObjectInputStream提供從輸入流中讀出物件的readObject方法。使用這些方法的物件必須已經被序列化的。也就是說,必須已經實作 Serializable介面。如果你想writeobject一個hashtable對象,那麼,會得到一個異常。
序列化的過程就是物件寫入位元組流和從位元組流讀取物件。將物件狀態轉換成位元組流之後,可以用Java.io套件中的各種位元組流類別將其儲存到檔案中,管道到另一個執行緒或透過網路連線將物件資料傳送到另一主機。物件序列化功能非常簡單、強大,在RMI、Socket、JMS、EJB都有應用。物件序列化問題在網路程式設計中並不是最令人興奮的課題,但卻相當重要,具有許多實用意義。
物件序列化可以實現分散式物件。主要應用例如:RMI要利用物件序列化來運行遠端主機上的服務,就像在本機上執行物件時一樣。
Java物件序列化不僅保留一個物件的數據,而且遞歸保存物件所引用的每個物件的數據。可以將整個物件層次寫入位元組流中,可以保存在檔案中或在網路連線上傳遞。利用物件序列化可以進行物件的“深複製”,即複製物件本身及引用的物件本身。序列化一個物件可能得到整個物件序列。
Java序列化比較簡單,通常不需要寫出保存和恢復物件狀態的客製化程式碼。實作Java.io.Serializable介面的類別物件可以轉換成位元組流或從位元組流恢復,不需要在類別中增加任何程式碼。只有在極少數情況下才需要定製程式碼保存或恢復物件狀態。這裡要注意:不是每個類別都可序列化,有些類別是不能序列化的,例如涉及執行緒的類別與特定JVM有非常複雜的關係。

序列化機制:

序列化分為兩大部分:序列化 和反序列化 。序列化是這個過程的第一部分,將資料分解成位元組流,以便儲存在檔案中或在網路上傳輸。反序列化就是開啟位元組流並重構物件。物件序列化不僅要將基本資料類型轉換成位元組表示,有時還要恢復資料。恢復資料要求有恢復資料的物件實例。 ObjectOutputStream中的序列化過程與位元組流連接,包括物件類型和版本資訊。反序列化時,JVM用頭資訊產生物件實例,然後將物件位元組流中的資料複製到物件資料成員中。下面我們分成兩大部分來闡述:

處理物件流:(序列化過程和反序列化過程)
Java.io套件有兩個序列化物件的類別。 ObjectOutputStream負責將物件寫入位元組流,ObjectInputStream從位元組流重構物件。
我們先來了解ObjectOutputStream類別吧。 ObjectOutputStream類別擴充DataOutput介面。
writeObject() 方法是最重要的方法,用於物件序列化。如果物件包含其他物件的引用,則writeObject()方法遞歸序列化這些物件。每個 ObjectOutputStream維護序列化的物件參考表,防止發送相同物件的多個拷貝。 (這點很重要)由於writeObject()可以序列化整組交叉引用的對象,因此同一ObjectOutputStream實例可能不小心被請求序列化同一對象。這時,進行反引用序列化,而不是再次寫入物件位元組流。

下面,讓我們從範例來了解ObjectOutputStream這個類別吧。

// 序列化 today's date 到一个文件中.  
FileOutputStream  f = new  FileOutputStream ("tmp" );  
ObjectOutputStream  s = new  ObjectOutputStream (f);  
s.writeObject("Today" );  
s.writeObject(new  Date ());  
s.flush();

現在,讓我們來了解ObjectInputStream這個類別。它與ObjectOutputStream相似。它擴展DataInput介面。 ObjectInputStream中的方法鏡像DataInputStream中讀取Java基本資料類型的公開方法。 readObject()方法從位元組流中反序列化物件。每次呼叫readObject()方法都會傳回流中下一個Object。物件位元組流並不傳輸類別的字節碼,而是包括類別名稱及其簽章。 readObject()收到物件時,JVM會裝入頭中指定的類別。如果找不到這個類,則readObject()拋出 ClassNotFoundException,如果需要傳輸物件資料和字節碼,則可以用RMI框架。 ObjectInputStream的其餘方法用於自訂反序列化過程。
範例如下:

//从文件中反序列化 string 对象和 date 对象  
FileInputStream  in = new  FileInputStream ("tmp" );  
ObjectInputStream  s = new  ObjectInputStream (in);  
String  today = (String )s.readObject();  
Date  date = (Date )s.readObject();
自訂序列化過程:
序列化通常可以自動完成,但有時可能要對這個過程進行控制。 java可以將類別宣告為serializable,但仍可手動控制宣告為static或transient的資料成員。

範例:一個非常簡單的序列化類別。

public  class  simpleSerializableClass implements  Serializable {  
String  sToday="Today:" ;  
transient  Date  dtToday=new  Date ();  
}

序列化時,類別的所有資料成員應可序列化除了宣告為transient 或static的成員。將變數宣告為transient告訴JVM我們會負責將變元序列化。將資料成員宣告為transient後,序列化過程就無法將其加進物件位元組流中,沒有從transient資料成員傳送的資料。後面資料反序列化時,要重建資料成員(因為它是類別定義的一部分),但不包含任何數據,因為這個資料成員不會在流中寫入任何資料。記住,物件流不序列化static或transient。我們的類別要用writeObject()與 readObject()方法來處理這些資料成員。使用writeObject()與readObject()方法時,也要注意以寫入的順序讀取這些資料成員。
關於如何使用自訂序列化的部分程式碼如下

//重写writeObject()方法以便处理transient的成员。  
public  void  writeObject(ObjectOutputStream  outputStream) throws  IOException {  
outputStream.defaultWriteObject();//使定制的writeObject()方法可以  
//利用自动序列化中内置的逻辑。  
outputStream.writeObject(oSocket.getInetAddress());  
outputStream.writeInt(oSocket.getPort());  
}  
//重写readObject()方法以便接收transient的成员。  
private  void  readObject(ObjectInputStream  inputStream) throws IOException ,  
ClassNotFoundException {  
inputStream.defaultReadObject();//defaultReadObject()补充自动序列化  
InetAddress  oAddress=(InetAddress )inputStream.readObject();  
int  iPort =inputStream.readInt();  
oSocket = new  Socket (oAddress,iPort);  
iID=getID();  
dtToday =new  Date ();  
}

完全自訂序列化過程:
如果一個類別要完全負責自己的序列化,則實作Externalizable介面而不是Serializable介面。 Externalizable介面定義包含兩個方法writeExternal()與readExternal()。利用這些方法可以控制物件資料成員如何寫入位元組流.類別實作 Externalizable時,頭寫入物件流中,然後類別完全負責序列化和復原資料成員,除了頭以外,根本沒有自動序列化。這裡要注意了。聲明類別實作 Externalizable介面會有重大的安全風險。 writeExternal()與readExternal()方法宣告為public,惡意類別可以用這些方法讀取並寫入物件資料。如果物件包含敏感資訊,則要格外小心。這包括使用安全套接或加密整個位元組流。到此為至,我們學習了序列化的基礎部分知識。

更多java教學之物件序列化使用基礎範例詳解相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn