F 序列化

Java 對象只有在虛擬機運行的情況下才存在,而虛擬機關閉了以後,這個對象也隨着內存回收被釋放掉,這種狀態稱爲“瞬態”。如何把這種瞬態轉換爲持久態就是序列化要解決的問題。除了持久化需要用到序列化以外,把一個對象在網絡上進行傳輸也是序列化的一個重要功能。在網絡上,數據以字節的形式進行傳輸,序列化可以把一個對象作爲整體在網絡上傳輸,在網絡的另一端,對這個整體進行還原。這樣就實現了以對象爲單位的傳輸。

 

1如何實現序列化

1.1默認的序列化

Java最簡單的序列化可以通過對一個類實現Serializable接口實現。Serializable接口是一個標識接口,並沒有定義任何方法。JDK中一些常見的類都實現了這個接口。如果想要對對象進行序列化,這個對象的類必須實現了Serilizable接口,否則將會拋出異常。下面是一個最簡單的實例,用於說明如何進行序列化以及反序列化。

(1)定義了一個類  實現了Serializable接口

class Sout implements Serializable
{
	public static  int  staticInt=1000;
	public int  paramA=1000;
	private String  paramB="abc";


}
(2) 序列化與反序列化,序列化使用ObjectOutputStream類的writeObject方法,反序列化使用ObjectInputStream的readObject方法

Sout sout=new Sout();
		sout.paramA=2500;
		
		//序列化
		ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("d:/a.dat"));
		objectOutputStream.writeObject(sout);
		objectOutputStream.close();
		
		//反序列化
		ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("d:/a.dat"));
		Sout sout2=(Sout)
		objectInputStream.readObject();

以上兩個過程就完成了最簡單,也是最常見的序列化方式。這種序列化將會對目標對象的所有屬性進行序列化,包括屬性是另一個對象的引用的情況,也將對其進行序列化。如果這個引用的對象沒有實現Serializable接口,將會拋出異常。這種序列化顯然不是最佳的,如果一個目標對象包含很多引用對象,這樣的序列化過程將會是十分消耗資源的,而且也是不安全的。

另外還需要注意的是,這種序列化方式只對當前類的屬性進行序列化,而不會對它的父類進行序列化,下面的例子將說明這一點

(1) 定義一個Father和一個Son類,子類Son類實現了Serializable接口

class Father
{
	public String name;
	public int age;

}

class Son extends Father implements Serializable
{
	public String sex = "male";

}

(2) 序列化的結果是 屬於Father類的屬性 name爲null

public static void main(String[] args) throws Throwable, IOException
	{
		Son son = new Son();
		son.name = "xiaoming";
		son.age = 20;

		// 序列化
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(
				new FileOutputStream("d:/a.dat"));
		objectOutputStream.writeObject(son);
		objectOutputStream.close();

		// 反序列化
		ObjectInputStream objectInputStream = new ObjectInputStream(
				new FileInputStream("d:/a.dat"));
		Son son2 = (Son) objectInputStream.readObject();

		System.out.println(son2.sex);
		System.out.println(son2.name);

	}

對於這種需要序列化父類的情況,它的解決辦法之一是讓父類實現Serializable接口。


1.2 實現writeObject和readObject方法

這是1.1的升級版,對於1.1中介紹的默認方法來說,它是不可控制的,完全由JDK封裝的方法對類進行序列化。這種方式的缺陷不僅僅如上面提到的,而且如果類中的屬性有被transient關鍵字修飾的時候,這種方式可以破壞這種約束。

來看下面的例子

(1)定義一個Person類,其中age字段被transient修飾,表示不可以被序列化。同時定義了writeObject和readObject方法

class Person implements Serializable
{
	public transient int age;
	public String name;

	private void writeObject(ObjectOutputStream out) throws IOException
	{
		out.defaultWriteObject();
//		out.writeInt(age);
	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException
	{
		in.defaultReadObject();
//		age = in.readInt();
	}

}

(2) 序列化的結果

如果去掉上面的註釋,age字段將同樣被序列化

對於這種方式實現序列化,這其中的defaultWriteObject和defaultReadObject兩個方法實際上就是1.1中的默認序列化方法。如果一個類既實現了Serializable接口,又定義了writeObject和readObject方法的話,在序列化時就調用這兩個方法進行序列化。而且這兩個方法可以在默認的序列化方式基礎上進行拓展,即可以實現transient修飾的屬性的序列化,又可以對父類的屬性進行序列化。

1.3 實現Externalizable接口

這種方式與第二種方式很類似,只不過它實現的Externalizable接口,而且需要實現writeExternal和readExternal這兩個方法。這是一種更加隨意的序列化的方式,想要序列化那些屬性,完成由自己決定。這種方式需要注意一點是,序列化的類需要提供一個空的構造方法。

示例:

class Person implements Externalizable
{
	public Person()

	{

	}

	public transient int age;
	public String name;

	@Override
	public void writeExternal(ObjectOutput out) throws IOException
	{
		out.writeInt(age);
		out.writeUTF(name);

	}

	@Override
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException
	{
		age = in.readInt();
		name = in.readUTF();
	}

}
注意:

writeObject readObject是私有的,只能被序列化機制調用,與此不同給的是,writeExternal和readExternal是公有的方法,而且還存在允許修改對象現有的狀態。

2 對單例的序列化

在對單例的類進行序列化的時候,如果按照上面的方式,那麼儘管得到了一致的結果,但並不滿足單例的要求。如下:


(1)對於下面的程序,雖然利用序列化產生了新的實例,但是它已經破壞了單例的特性。

SingleLady singleLady=SingleLady.getInstance();
		
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		// 序列化
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(
				outputStream);
		objectOutputStream.writeObject(singleLady);
		objectOutputStream.close();

		outputStream.toByteArray();

		// 反序列化

		ObjectInputStream objectInputStream = new ObjectInputStream(
				new ByteArrayInputStream(outputStream.toByteArray()));

		SingleLady singleLady2 = (SingleLady) objectInputStream.readObject();
		System.out.println(singleLady==singleLady2);
 class SingleLady implements Serializable {
	private static SingleLady singleLady;

	private SingleLady() {

	}

	public static SingleLady getInstance() {
		if (singleLady == null) {
			singleLady = new SingleLady();
			return singleLady;
		} else {
			return singleLady;
		}
	}

	public String getName() {
		return name;
	}

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

	private String name;


}


(2)這就需要一種特別的方式去實現單例的序列化。需要定義另一種稱爲readResolve的特殊序列化方法僅需在單例類中定義這個方法。

/**
	 * 單例的序列化方法
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		return SingleLady.getInstance();
	}



3SerialVersionUID與版本管理

在定義一個類實現了Serializable接口的時候,編譯器通常會給出這麼一個警告,提示我們需要生成一個 serial version ID。

這個ID到底有什麼用?簡單的說,它相當於類的版本號,這樣它就可以進行反序列化。當一個類進行反序列化的時候,首先要檢驗SerialVersionUID,如果在反序列化的過程中,SerialVersionUID不匹配的話,這個反序列化的過程就會失敗,同時會拋出java.io.InvalidClassException異常,並打印出類的名字以及對應的SerialVersionUID。

當一個類升級的時候,它的所有較新版本都必須把serialVersionUID常量定義爲與最初的版本的指紋相同。一旦這個靜態成員被置於類中,那麼序列化系統就可以讀入這個類的不同版本。如果這個類只有方法發生了變化,那麼在讀入新的對象時不會產生任何問題。但是如果數據域發生了改變,這個時候可能會存在一些問題,對象流也會盡力地把這個對象轉換到當前版本上:

  • 對象流會將對象與當前版本進行對比,而且僅僅對比非瞬時和非靜態的數據域。如果兩個數據域名字相同而類型卻不同的話,那麼對象流不會進行轉換;

  • 如果對象流中的數據域在當前類中不存在,那麼對象流將會忽略掉這些數據

  • 如果當前版本存在對象流中不存在的數據,那麼這些數據將會被設置成默認值。

  • 如果使用序列化來保存對象,尤其是把一個對象保存到文件中,就需要考慮程序的演化對這個對象的影響。主要的影響就是對屬性的增加與刪除



4重複引用的序列化

在實際中我們可能遇到這種情況,兩個對象都持有另一個對象的引用,那麼在序列化的時候,顯然不應該把這個被引用的對象序列化兩次。對於這種情況,Java利用了序列號的機制來解決這個問題:

  • 對遇到的每一個引用都關聯一個序列號;

  • 每個對象 第一次遇到的時候,保存其對象到數據流中;

  • 如果某個對象已經保存過,那麼只寫出“與之前保存過的序列號爲x的對象相同”。在讀對象時,整個過程反過來

  • 對於流中的對象,第一次遇到它的序列號時,構建它,並使用流中數據來初始化它,然後記錄這個序列號和新對象之間的關聯;

  • 當遇到“與之前保存過的序列號爲x的對象相同”標記時,獲取與這個順序號相關聯的對象引用;

通過下面的示例可以看出Java是如何實現對重複引用的序列化的

(1) 定義兩個類  common和hold


class Common implements Serializable
{
	/**
	 * 
	 */
	//private static final long serialVersionUID = -393917310072427835L;
	private static int count=0;
	private void writeObject(ObjectOutputStream out) throws IOException { 
		System.out.println( "common write invoked  " +(count++));
        out.defaultWriteObject();  
       
    }  
 
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 
    	System.out.println(" common  read invoked");
        in.defaultReadObject();  
        
    }  

class Hold implements Serializable
{
	private Common common;

	public Common getCommon() {
		return common;
	}

	public void setCommon(Common common) {
		this.common = common;
	}
	

}

(2) 序列化比較結果


public static void main(String[] args) throws Throwable {
		Common common=new Common();
		Hold hold1=new Hold();
		Hold hold2=new Hold();
		
		hold1.setCommon(common);
		hold2.setCommon(common);
		
		
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		// 序列化
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(
				outputStream);
		objectOutputStream.writeObject(hold1);
		objectOutputStream.writeObject(hold2);
		objectOutputStream.close();

		outputStream.toByteArray();

		// 反序列化

		ObjectInputStream objectInputStream = new ObjectInputStream(
				new ByteArrayInputStream(outputStream.toByteArray()));

		Hold hold3 = (Hold) objectInputStream.readObject();
		Hold hold4 = (Hold) objectInputStream.readObject();
		
		System.out.println(hold3.getCommon()==hold4.getCommon());
	}

結果顯示序列化過程僅僅調用了一次 ,而且反序列化得到的結果也是指向同一個引用的。證明了上面說的Java序列化對於相同引用的機制。

5 clone

在Object類中,定義了protected的Clone方法,當需要進行clone時,這個方法必須被重寫。這樣很麻煩,而且某種程度上說也不太安全。序列化機制可以巧妙地從另外一個角度去解決這個問題,做法很簡單。把一個對象序列化到流,然後進行反序列化。這樣就產生了一個深拷貝。

class StudentNew implements Serializable, Cloneable {
	public String name;
	public ClassMate classMate;

	public  StudentNew clone() throws CloneNotSupportedException {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		try {
			// 序列化
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(
					outputStream);
			objectOutputStream.writeObject(this);
			objectOutputStream.close();

			outputStream.toByteArray();

			// 反序列化

			ObjectInputStream objectInputStream = new ObjectInputStream(
					new ByteArrayInputStream(outputStream.toByteArray()));

			StudentNew cloneStudent = (StudentNew) objectInputStream.readObject();
			return cloneStudent;
		} catch (Exception e) {
			return null;
		}

	}
}




 

 

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