天天看點

java序列化

1、序列化是幹什麼的?

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

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

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

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

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

3、當對一個對象實作序列化時,究竟發生了什麼?

在沒有序列化前,每個儲存在堆(heap)中的對象都有相應的狀态(state),即執行個體變量(instance ariable)比如:

java 代碼

foo  myfoo = new foo();

myfoo .setwidth(37);

myfoo.setheight(70);

當 通過下面的代碼序列化之後,myfoo對象中的width和height執行個體變量的值(37,70)都被儲存到foo.ser檔案中,這樣以後又可以把它 從檔案中讀出來,重新在堆中建立原來的對象。當然儲存時候不僅僅是儲存對象的執行個體變量的值,jvm還要儲存一些小量資訊,比如類的類型等以便恢複原來的對 象。

4、實作序列化(儲存到一個檔案)的步驟

a、make a fileoutputstream

fileoutputstream fs = new fileoutputstream(“foo.ser”);

b、make a objectoutputstream

c、write the object

d、 close the objectoutputstream

os.close();

5、舉例說明

java代碼

package com.hotye.dchaoxiong.serializabletest;

import java.io.fileinputstream;

import java.io.fileoutputstream;

import java.io.objectinputstream;

import java.io.objectoutputstream;

import java.io.serializable;

public class box implements serializable {

/**

*

*/

private static final long serialversionuid = 1l;

private int width;

private int height;

private string name;

public static void main(string[] args) {

try {

box mybox = new box();

mybox.setwidth(50);

mybox.setheight(30);

mybox.setname(“雕戈”);

fileoutputstream fs = new fileoutputstream(“serializableobject.txt”);

objectoutputstream os = new objectoutputstream(fs);

os.writeobject(mybox);

fs.close();

} catch (exception ex) {

ex.printstacktrace();

}

fileinputstream fis = new fileinputstream(“serializableobject.txt”);

objectinputstream ois = new objectinputstream(fis);

box box = (box) ois.readobject();

system.out.println(box.getwidth());

system.out.println(box.getheight());

system.out.println(box.getname());

ois.close();

fis.close();

} catch (exception e) {

e.printstacktrace();

public void setheight(int height) {

this.height = height;

public int getheight() {

return height;

public void setwidth(int width) {

this.width = width;

public int getwidth() {

return width;

public void setname(string name) {

this.name = name;

public string getname() {

return name;

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();

完全定制序列化過程:

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

=========================================================================

以下來源于j2ee api:

對象的預設序列化機制寫入的内容是:對象的類,類簽名,以及非瞬态和非靜态字段的值。其他對象的引用(瞬态和靜态字段除外)也會導緻寫入那些對象。可使用引用共享機制對單個對象的多個引用進行編碼,這樣即可将對象的圖形還原為最初寫入它們時的形狀。

例如,要寫入可通過 objectinputstream 中的示例讀取的對象,請執行以下操作:

fileoutputstream fos = new fileoutputstream(“t.tmp”);

objectoutputstream oos = new objectoutputstream(fos);

oos.writeint(12345);

oos.writeobject(“today”);

oos.writeobject(new date());

oos.close();

在序列化和反序列化過程中需要特殊處理的類必須實作具有下列準确簽名的特殊方法:

private void readobject(java.io.objectinputstream stream)

throws ioexception, classnotfoundexception;

private void writeobject(java.io.objectoutputstream stream)

throws ioexception

writeobject 方法負責寫入特定類的對象狀态,以便相應的 readobject 方法可以還原它。該方法本身不必與屬于對象的超類或子類的狀态有關。狀态是通過使用 writeobject 方法或使用 dataoutput 支援的用于基本資料類型的方法将各個字段寫入 objectoutputstream 來儲存的。

序列化操作不寫出沒有實作 java.io.serializable 接口的任何對象的字段。不可序列化的 object 的子類可以是可序列化的。在此情況下,不可序列化的類必須有一個無參數構造方法,以便允許初始化其字段。在此情況下,子類負責儲存和還原不可序列化的類的 狀态。經常出現的情況是,該類的字段是可通路的(public、package 或 protected),或者存在可用來還原狀态的 get 和 set 方法。

實作 writeobject 和 readobject 方法可以阻止對象的序列化,這時抛出 notserializableexception。objectoutputstream 導緻發生異常并中止序列化程序。

實 現 externalizable 接口允許對象假定可以完全控制對象的序列化形式的内容和格式。調用 externalizable 接口的方法(writeexternal 和 readexternal)來儲存和恢複對象的狀态。通過類實作時,它們可以使用 objectoutput 和 objectinput 的所有方法讀寫它們自己的狀态。對象負責處理出現的任何版本控制。

enum 常量的序列化不同于普通的 serializable 或 externalizable 對象。enum 常量的序列化形式隻包含其名稱;常量的字段值不被傳送。為了序列化 enum 常量,objectoutputstream 需要寫入由常量的名稱方法傳回的字元串。與其他 serializable 或 externalizable 對象一樣,enum 常量可以作為序列化流中後續出現的 back 引用的目标。用于序列化 enum 常量的程序不可定制;在序列化期間,由 enum 類型定義的所有類特定的 writeobject 和 writereplace 方法都将被忽略。類似地,任何 serialpersistentfields 或 serialversionuid 字段聲明也将被忽略,所有 enum 類型都有一個 0l 的固定的 serialversionuid。

基本資料(不包括 serializable 字段和 externalizable 資料)以塊資料記錄的形式寫入 objectoutputstream 中。塊資料記錄由頭部和資料組成。塊資料部分包括标記和跟在部分後面的位元組數。連續的基本寫入資料被合并在一個塊資料記錄中。塊資料記錄的分塊因子為 1024 位元組。每個塊資料記錄都将填滿 1024 位元組,或者在終止塊資料模式時被寫入。調用 objectoutputstream 方法 writeobject、defaultwriteobject 和 writefields 最初隻是終止所有現有塊資料記錄。