黑馬程序員-序列化與反序列化

----------- android培訓java培訓、java學習型技術博客、期待與您交流! ------------

當兩個進程在進行遠程通信時,彼此可以發送各種類型的數據。無論是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方需要把這個Java對象轉換爲字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復爲Java對象。

  把Java對象轉換爲字節序列的過程稱爲對象的序列化。

  把字節序列恢復爲Java對象的過程稱爲對象的反序列化。

  對象的序列化主要有兩種用途:

  1) 把對象的字節序列永久地保存到硬盤上,通常存放在一個文件中;

  2) 在網絡上傳送對象的字節序列。

  一. JDK類庫中的序列化API

  java.io.ObjectOutputStream代表對象輸出流,它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。

  java.io.ObjectInputStream代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化爲一個對象,並將其返回。、

  只有實現了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行爲,而僅實現Serializable接口的類可以採用默認的序列化方式 。

  對象序列化包括如下步驟:

  1) 創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,如文件輸出流;

  2) 通過對象輸出流的writeObject()方法寫對象。

  對象反序列化的步驟如下:

  1) 創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,如文件輸入流;

  2) 通過對象輸入流的readObject()方法讀取對象。

 二.實現Serializable接口

  ObjectOutputStream只能對Serializable接口的類的對象進行序列化。默認情況下,ObjectOutputStream按照默認方式序列化,這種序列化方式僅僅對對象的非transient的實例變量進行序列化,而不會序列化對象的transient的實例變量,也不會序列化靜態變量。

  當ObjectOutputStream按照默認方式反序列化時,具有如下特點:

  1) 如果在內存中對象所屬的類還沒有被加載,那麼會先加載並初始化這個類。如果在classpath中不存在相應的類文件,那麼會拋出ClassNotFoundException;

  2) 在反序列化時不會調用類的任何構造方法。

    三. 可序列化類的不同版本的序列化兼容性

  凡是實現Serializable接口的類都有一個表示序列化版本標識符的靜態變量:

       serialVersionUID的取值是Java運行時環境根據類的內部細節自動生成的。如果對類的源代碼作了修改,再重新編譯,新生成的類文件的serialVersionUID的取值有可能也會發生變化。

  類的serialVersionUID的默認值完全依賴於Java編譯器的實現,對於同一個類,用不同的Java編譯器編譯,有可能會導致不同的serialVersionUID,也有可能相同。爲了提高哦啊serialVersionUID的獨立性和確定性,強烈建議在一個可序列化類中顯示的定義serialVersionUID,爲它賦予明確的值。顯式地定義serialVersionUID有兩種用途:

  1) 在某些場合,希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有相同的serialVersionUID;

  2) 在某些場合,不希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有不同的serialVersionUID。

 

package com.itcast.test1;
import java.util.*;
import java.io.*;
public class SerializableTest {
	/**
	 * 對象序列化與反序列化
	 * 進行對象序列化主要目的是爲了保存對象的狀態(成員變量)。
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<Ball> list = new ArrayList<Ball>();
		Ball ball1 = new Ball("basketball",1);
		Ball ball2 = new Ball("football",2);
		Ball ball3 = new Ball("badminton",3);
		list.add(ball1);
		list.add(ball2);
		list.add(ball3);
		try {
			serialize(list);
			deserialize("obj.ser");
		} catch(Exception e) {
			e.printStackTrace();
		}	
	}
	/**
	 * 進行序列化主要用到的流是FileOutputStream和ObjectOutputStream。
	 * FileOutputStream主要用於連接磁盤文件,並把字節寫出到該磁盤文件;
	 * @param list
	 * @throws IOException
	 */
	public static void serialize(List<Ball> list) throws IOException {
		FileOutputStream fos = new FileOutputStream("obj.ser");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		for(Ball ball : list) {
			oos.writeObject(ball);
		}
	}
	/**
	 * 保存狀態的目的就是爲了在未來的某個時候再恢復保存的內容,
	 * 這可以通過反序列化來實現。對象的反序列化過程與序列化正好相反,
	 * 主要用到的兩個流是FileInputstream和ObjectInputStream。
	 * 反序列化後得到的對象的順序與保存時的順序一致。
	 * @param fileName
	 * @throws Exception
	 */
	public static void deserialize(String fileName) throws Exception {
		FileInputStream fis = new FileInputStream(fileName);
		ObjectInputStream ois = new ObjectInputStream(fis);
		while (true) {
			try {
					Ball ball = (Ball) ois.readObject();
					System.out.println(ball.toString());
			} catch (EOFException e) {
				System.out.println("文件已到末尾");
				break;
			}
		}
	}
}

/**
 * 補充一:上面我們舉得例子很簡單,要保存的成員變量要麼是基本類型的要麼是String類型的。
 * 但有時成員變量有可能是引用類型的,這是的情況會複雜一點。那就是當要對某對象進行序列化時,
 * 該對象中的引用變量所引用的對象也會被同時序列化,並且該對象中如果也有引用變量的話則該對象
 * 也將被序列化。總結說來就是在序列化的時候,對象中的所有引用變量所對應的對象將會被同時序列化。
 * 這意味着,引用變量類型也都要實現Serializable接口。當然其他對象的序列化都是自動進行的。
 * 所以我們只要保證裏面的引用類型是都實現Serializable接口就行了,如果沒有的話,會在編譯時拋出異常。
 * 如果序列化的對象中包含沒有實現Serializable的成員變量的話,這時可以使用transient關鍵字,
 * 讓序列化的時候跳過該成員變量。使用關鍵字transient可以讓你在序列化的時候自動跳過transient所修飾的成員變量,
 * 在反序列化時這些變量會恢復到默認值。
 * 補充二:如果某類實現了Serializable接口的話,其子類會自動編程可序列化的,這個好理解,繼承嘛。
 * 補充三:在反序列化的時候,並不會調用對象的構造器,這也好理解,如果調用了構造器的話,對象的狀態不就又重新初始化了嗎。
 * 補充四:我們說到對象序列化的是爲了保存對象的狀態,即對象的成員變量,所以靜態變量不會被序列化。
 */
package com.itcast.test1;

import java.io.Serializable;

/**
 * javabean多用於封裝屬性,而很少具備某些特定的方法功能。
 * 試想,javabean的功能就是傳遞數據,那麼類權限爲共有也
 * 就不足爲奇了。下面是標準javabean要遵守的一些規範:
 *1. 類訪問權限爲公有
 *2. 所有屬性爲私有
 *3. 每個字段對外提供setter方法和getter方法
 *4. 具備一個無參的構造方法
 */
public class Ball implements Serializable {
	private static final long serialVersionUID = 1L;
	/**
	 * 實現序列化
	 * 要將某類的對象序列化,則該類必須實現Serializable接口,該接口
	 * 僅是一個標誌,告訴JVM該類的對象可以被序列化。如果某類未實現Serializable接口,
	 * 則該類對象不能實現序列化。
	 */
	private String ballName;
	private int serialNumber;
	public Ball(String ballName, int serialNumber) {
		super();
		this.ballName = ballName;
		this.serialNumber = serialNumber;
	}
	public String getBallName() {
		return ballName;
	}
	public void setBallName(String ballName) {
		this.ballName = ballName;
	}
	public int getSerialNumber() {
		return serialNumber;
	}
	public void setSerialNumber(int serialNumber) {
		this.serialNumber = serialNumber;
	}
	@Override
	public String toString() {
		return "Ball [ballName=" + ballName + ", serialNumber=" + serialNumber
				+ "]";
	}	
}


 

----------- android培訓java培訓、java學習型技術博客、期待與您交流! ------------

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