天天看點

Java序列化的作用和反序列化

1、序列化是幹什麼的?

  簡單說就是為了儲存在記憶體中的各種對象的狀态(也就是執行個體變量,不是方法),并且可以把儲存的對象狀态再讀出來。雖然你可以用你自己的各種各樣的方法來保 存object states,但是java給你提供一種應該比你自己好的儲存對象狀态的機制,那就是序列化。

  2、什麼情況下需要序列化

  a)當你想把的記憶體中的對象狀态儲存到一個檔案中或者資料庫中時候;

  b)當你想用套接字在網絡上傳送對象的時候;

  c)當你想通過rmi傳輸對象的時候;

  6、相關注意事項

  a)序列化時,隻對對象的狀态進行儲存,而不管對象的方法;

  b)當一個父類實作序列化,子類自動實作序列化,不需要顯式實作serializable接口;

  c)當一個對象的執行個體變量引用其他對象,序列化該對象時也把引用對象進行序列化;

  d)并非所有的對象都可以序列化,,至于為什麼不可以,有很多原因了,比如:

  1.安全方面的原因,比如一個對象擁有private,public等field,對于一個要傳輸的對象,比如寫到檔案,或者進行rmi傳輸 等等,在序列化進行傳輸的過程中,這個對象的private等域是不受保護的。

  2. 資源配置設定方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者儲存,也無法對他們進行重新的資源分 配,而且,也是沒有必要這樣實作。

  較長的描述:

  序 列化的過程就是對象寫入位元組流和從位元組流中讀取對象。将對象狀态轉換成位元組流之後,可以用java.io包中的各種位元組流類将其儲存到檔案中,管道到另一 線程中或通過網絡連接配接将對象資料發送到另一主機。對象序列化功能非常簡單、強大,在rmi、socket、jms、ejb都有應用。對象序列化問題在網絡 程式設計中并不是最激動人心的課題,但卻相當重要,具有許多實用意義。

  一:對象序列化可以實作分布式對象。主要應用例如:rmi要利用對象序列化運作遠端主機上的服務,就像在本地機上運作對象時一樣。

  二:java 對象序列化不僅保留一個對象的資料,而且遞歸儲存對象引用的每個對象的資料。可以将整個對象層次寫入位元組流中,可以儲存在檔案中或在網絡連接配接上傳遞。利用 對象序列化可以進行對象的“深複制”,即複制對象本身及引用的對象本身。序列化一個對象可能得到整個對象序列。

  從上面的叙述中,我們知道了對象序列化是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”); //建立一個包含恢複對象(即對象進行反序列化資訊)的”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();