對象序列化

對象序列化

對象序列化的目標是將對象保存到磁盤中,或允許在網絡中直接傳輸對象,對象序列化機制運行把內存中的Java對象轉換成平臺無關的二進制流,從而允許把這個二進制流持久保存在磁盤上,通過網絡將這種二進制流傳輸到另一個網絡節點。其他程序一旦獲得了這個二進制流,都可以將這種二進制流恢復成原來的Java對象。

對象序列化將一個Java對象寫入IO流中,對象的反序列化則是從IO流中恢復原的該Java對象。

Java對象實現序列化的方式:

(1)實現Serializaable接口,該接口是一個標記接口,實現該接口無須實現任何方法,它只是表明該類的實例是可以序列化的。

(2)實現Externalizable接口,


使用對象流實現序列化

通過對象流實現序列化的步驟:

(1)創建一個ObjectOutputStream輸出流。

(2)調用OjbectOutputStream對象的writeObject方法輸出可序列化對象。

經典實例:

public class SerializableTest {

	public static void main(String[] args) {
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("D:\\test.txt"));
			Person p = new Person("Android", 7);
			// 將p對象寫入輸出流
			oos.writeObject(p);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}
}

class Person implements Serializable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

}

反序列化的步驟:

(1)創建一個ObjectInputStream輸入流。

(2)調用ObjectInputStream對象的readObject方法讀取流中的對象。該方法返回一個Object類型的java對象。

經典實例:

public static void main(String[] args) {
		ObjectInputStream ois = null;
		try {
			//創建一個ObjectInputStream輸出流
			ois = new ObjectInputStream(new FileInputStream("D:\\test.txt"));
			//從流中讀取一個java對象
			Person p = (Person) ois.readObject();
			System.out.println("name=" + p.getName() + "  ,age=" + p.getAge());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}

反序列化機制無須通過構造器來初始化Java對象。如果反序列化某個子類實例時,反序列化機制需要恢復其關聯的父類實例,恢復這些父類實例有兩種方式:使用反序列化機制;使用父類無參數的構造函數。

如果一個可序列化類有多個父類(直接父類或間接父類),則該類的所有父類要麼是可序列化的,要麼有無參數的構造器,否則序列化時拋出IvaalidClassException異常。


對象引用的序列化

當程序序列化一個對象時,如果該對象持有一個其他對象的引用,爲了在反序列化時可以正常恢復該對象,則程序會順便把他引用的其他對象也進行序列化,否則該對象不能進行序列化。

Java序列化機制採用了一種特殊的序列化算法,其內容如下:

(1)所有保存到磁盤中的對象都有一個序列化編號。

(2)當程序試圖序列化一個對象時,程序將先檢測該對象是否已經序列化過,只有當該對象從未被序列化過,系統纔會將該對象轉換成字節序列並輸出。

(3)如果某個對象是已經序列化過的,程序將直接只輸出一個序列化編號,而不是再次重新序列化該對象。

注意:當使用Java序列化機制序列化可變對象時,一定要注意,只有當第一次調用wirteObject方法來輸出對象時纔會將對象轉換爲字節序列,並寫出到ObjectOutputStream;在後面程序中,如果該對象的屬性發送改變,即再次調用wirteObject方法輸出該對象時,改變後的屬性不會被輸出。


自定義序列化

遞歸序列化:當對某個對象進行序列化時,系統會自動把該對象的所有屬性依次進行序列化,如果某個屬性引用到另一個對象,則被引用的對象也會被序列化。如果被引用的對象的屬性也應用了其他對象,則被引用的對象也會被序列化。

通過在屬性前加transient關鍵字,可以指定Java序列化時無須理會該屬性,transient關鍵字只能用來修飾屬性,不可以修飾Java程序中其他部分。比如: private  transient  int  age;

但使用transient關鍵字修飾屬性雖然很簡單方便,但被transient修飾的屬性將被完全隔離在序列化機制之外,這樣導致在反序列化恢復Java對象時無法獲得該屬性值。Java提供了一種自定義序列化機制,通過這種自定義序列化機制可以讓程序控制如何序列化各屬性,甚至完全不序列化某些屬性。

自定義序列化需要對如下個方法進行處理

(1)private  void  writeObject(java.io.ObjectOutputStream  out) throws IOException;

(2)private  void  readObject(java.io.ObjectInputStream  in) throws IOException,ClassNotFoundException;

(3)private  void  readObjectNoData()throws ObjectStreamException;

writeObject方法負責寫入特定類的實例的狀態,以便相應的readObject方法可以恢復它。通過重寫該方法,程序員可以完全獲得對序列化機制的控制,程序員可以自主決定那些屬性需要序列化,需要怎樣序列化。默認情況下,該方法會調用out.defaultWriteObject來保存Java對象的各屬性,從而可以實現序列化Java對象狀態的目的。

readObject方法負責從流中讀取並恢復對象屬性,通過重寫該方法,程序員可以完全獲得對反序列化機制的控制,可以自主決定需要反序列化那些屬性,以及進行怎樣的反序列化。默認情況下,該方法會調用in.defaultReadObject來恢復Java對象的非靜態屬性,通常情況下readObject方法與readObject對應,如果writeObject方法中對Java對象的屬性進行了一些處理,則應該在readObject方法中對該屬性進行相應的反處理,以便正確恢復該對象。

當序列化流不完整時,readObjectNoData方法可以用來正確地初始化反序列化的對象。

經典實例:

public class Person implements Serializable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		// TODO Auto-generated method stub
		out.writeObject(new StringBuffer(name).reverse());
		out.writeInt(age);

	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		this.name = ((StringBuffer) in.readObject()).reverse().toString();
		this.age = in.readInt();
	}

}
上面程序中與前面說的序列化和反序列化的區別是:序列化後的對象流,即使有Cracker獲取到Person對象流,他看到name將是我們加密後的name指,這樣就提高了序列化的安全性。注意,writeObject方法存儲屬性的順序應該和readObject方法中恢復屬性的順序一致,否則將不能正常恢復該Java對象。

還有一種更測底的自定義機制,它可以在序列化對象時將那個該對象替換成其他對象。

ANY-ACCESS-MODIFIER  Object  writeReplace() throws ObjectStreamException;

此writeReplace方法將有序列化機制調用,前提是該方法存在。

public class Person implements Serializable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	//從寫wirteReplace方法,程序在序列化該對象之前,先調用該方法
	private Object wirteReplace()  throws ObjectStreamException {
		// TODO Auto-generated method stub
		ArrayList<Object> list=new ArrayList<Object>();
		list.add(name);
		list.add(age);
		return list;
	}

}
我們知道系統在序列化某個對象之前,會先調用該對象的如下兩個方法:writeReplace和writeObject,系統總是先調用序列化對象的writeReplace方法,如果該方法返回另一個對象,系統將再次調用另一個對象的writeReplace方法.........直到該方法不再返回另一個對象爲止,最後程序調用該對象的writeObject方法來保存該對象的狀態。

與writeReplace方法相對的是,序列化機制還有一個特殊的方法,他可以實現保護性負責整個對象。

ANY-ACCESS-MODIFIER  Object   readResolve() throws ObjectStreamException;

這個方法會緊接着readObject方法之後調用,該方法的返回值將會替代原來反序列化的對象,而原來readObject反序列化的對象將會被立即丟棄。


Externalizable自定義序列化機制

Java還提供了另一種序列化機制,這種序列化方式完全由程序員決定存儲和恢復對象數據,要實現該目的,java類必須實現Externalizable接口。

Externalizable接口定義的兩個方法:

(1)void   readExternal(ObjectInput  in):需要序列化的類實現readExternal方法來實現反序列化。該方法調用DataInput(它是ObjectInput的父接口)的方法來恢復基本類似的屬性值,調用ObjectInput的readObject方法來恢復引用類型的屬性值。

(2)void   writeExternal(ObjectOutput  out):需要序列化的類實現writeExternal方法來保存對象的狀態,該方法調用DataInput(它是ObjectInput的父接口)的方法來保存基本類型的屬性值,,調用ObjectOutput的writeObject方法來保存引用類型的屬性值。

經典實例:

public class Person implements Externalizable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeObject(new StringBuffer(name).reverse());
		out.writeInt(age);
	}
	@Override
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		// TODO Auto-generated method stub
		this.name = ((StringBuffer) in.readObject()).reverse().toString();
		this.age = in.readInt();
	}
}


兩種序列化機制的對比:


兩種序列化機制的對比
實現Serializable接口 實現Externalizable接口
系統自動存儲必要的信息 程序員決定存儲那些信息
Java內建支持,易於實現,只需實現該接口即可 僅僅提供兩個空方法,實現該接口必須爲兩個空方法提供實現
性能略差 性能略高

總結:雖然實現Externalizable節誒可能帶來一定的性能提升,由於實現Externalizable接口導致了編程度的增加,所以大部分還是採用實現Serializable接口來實現序列化。

對象序列化注意事項:

(1)對象的類名、屬性(包括基本類型,數組,對其他對象的引用)都會被序列化;方法、static屬性(即靜態屬性)、transient屬性都不會被序列化。

(2)實現Serializable接口的類如果需要想讓某個屬性不被序列化,可以在該屬性前加transient修飾符,而不是加static關鍵字,雖然能達到效果,但static不能這樣使用。

(3)保證序列化對象的屬性的類型是可以序列化的,否則需要使用transient關鍵字來修飾該屬性,要不然,則該類是不可序列化的。

(4)反序列化對象時必須有序列化對象的class文件。

(5)當通過文件、網絡來讀取實例化後的對象時,必須按實際寫入的順序讀取。


版本

反序列化Java對象時必須提供該對象的class文件,如果項目升級,系統的class文件同樣也會升級。爲了避免升級後和升級前的兼容性。Java序列化機制允許序列化類提供一個private static final 的serialVersionUID屬性值,該屬性值用於標識該Java類的序列化版本,也就是說,如果升級後和升級前的serialVersionUID保持不變,序列化機制也會把它們當成同一個序列化版本。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章