Java IO : ObjectInputStream、ObjectOutputStream

Java 對象序列化

java平臺允許在內存中創建可複用的java對象,但一般情況下,只有當JVM處於運行時,這些對象纔可能存在,即這些對象的生命生命週期不會比JVM生命週期長。但在現實應用中,就可能要求在JVM停止運行之後能夠保存(持久化)指定的對象,並在將來重新讀取被保存的對象。 Java對象序列化就能夠實現該功能。使用Java 對象序列化,在保存對象時,會將其轉換爲字節序列,這些序列可以保存在磁盤上,或通過網絡傳輸,以備以後重新恢復成原來的對象。

序列化緣由

有時候我們想把一個java對象寫入到磁盤文件或傳輸到網絡到其他計算機,這時我們就需要去把這個對象轉換成字節流,另一端接收字節流將其恢復成java對象。所以就有了java序列化的概念了。

好處

  • 實現了數據的持久化
  • 實現 遠程通信

序列化ID

序列化ID 提供兩種生成策略: 一是固定的1L,一個是隨機生成一個不重複的long類型數據(實際上是使用JDK工具生成) 。
Java 的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致,在進行反序列化時,JVM會把傳過來的字節流中的serialVersionUID與本地相應實體(類) 的serialVersionUID進行比較,如果相同,則進行反序列化,否則就會出現序列化版本不一致的異常。

如何實現序列化

  • 實現Serializable 接口即可
    當實現Serializable 接口的實體沒有顯示定義一個名爲serialVersionUID,類型爲long變量時,java序列化機制會根據編譯的class自動生成一個serialVersionUID做序列化版本比較,這種情況下,只有通一次編譯生成的class纔會生成相同的serialVersionUID。

實例:

普通對象序列化

public class UserInfo implements Serializable{
	
	private static final long serialVersionUID = 1L;
	private String userName;
	private int age; 	
	private String address;
	public UserInfo(String userName, int age, String address) {
		super();
		System.out.println("有參數的構造器");
		this.userName = userName;
		this.age = age;
		this.address = address;
	}
	..... // get、set 方法省略
}
/**
 * ObjectStream
 * 
 * @author mingx
 *
 */
public class ObjectStream {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		ObjectOutputStream oos = null;
		ObjectInputStream oin = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("E:/javaFile/object.txt"));
			UserInfo userInfo = new UserInfo("KANNO爺", 25, "幽冥谷");
			// 將對象寫入輸出流
			oos.writeObject(userInfo);
			// 反序列化操作
			oin = new ObjectInputStream(new FileInputStream("E:/javaFile/object.txt"));
			UserInfo user = (UserInfo) oin.readObject();
			System.out.println("名字爲:" + user.getUserName() + "\n 年齡爲:" + user.getAge() + "\n地址爲:" + user.getAddress());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				oos.close();
			}
			if (oin != null) {
				oin.close();
			}
		}
	}
}

序列化: 上面的程序創建ObjectOutputStream,這個輸出流建立在一個文件輸出流的基礎上,使用writeObject() 方法將一個UserInfo對象寫入輸出流,運行上面程序生成一個object.txt文件,該文件的內容就是UserInfo對象。
反序列化:創建一個ObjectInputStream,這個輸入流是一個處理流,所以必須建立在其他節點流的基礎上,調用readObject() 方法讀取流中的對象,該方法返回一個Object類型的Java對象,如果程序知道該java對象的類型,將強制轉換成其真實類型。

注意:UserInfo類中只定義了一個有參數的構造函數,當我們反序列化讀取Java對象時,並沒有調用該構造函數,這表明反序列化機制無須通過構造器來初始化Java對象。
如果一個可序列化有多個父類,則該類的所有父類要麼是可序列化的,要麼有無參數的構造器,否則反序列化時會拋出InvalidClassException異常。

對象引用序列化

如果某個類的屬性類型是一個引用類型,那麼這個引用類型必須是可序列哈的,否則擁有該類型屬性的類是不可序列化的。例如:

public class ObjectReferenceSeriable {
	
	public static void main(String[] args) throws IOException, ClassNotFoundException{
		
		ObjectOutputStream out = null; 
		ObjectInputStream oin = null; 
		try {
			//序列化操作
			out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\teacher.txt"));
			UserInfo userInfo = new UserInfo("龍幽", 25); 
			Teacher t1 = new Teacher("淨天教", userInfo); 
			Teacher t2 = new Teacher("蠻族", userInfo);
			// 依次將四個對象寫入輸出流
			out.writeObject(t1);
			out.writeObject(t2);
			out.writeObject(userInfo);
			out.writeObject(t2);
			
			//反序列化操作
			oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\teacher.txt")); 
			// 依次讀取輸入流的四個對象
			Teacher T1 = (Teacher) oin.readObject(); 
			Teacher T2 = (Teacher) oin.readObject(); 
			UserInfo user= (UserInfo) oin.readObject(); 
			Teacher T3 = (Teacher) oin.readObject();
			System.out.println("T1的student引用和user是否相同:" + (T1.getStudent() == user));
			System.out.println("T2的student引用和user是否相同:" + (T2.getStudent() == user));
			System.out.println("T2和T3是否同一個對象:" + (T2==T3));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally {
			if(out !=null){
				out.close();
			}
			if(oin !=null){
				oin.close();
			}
		}
		
	}
	public static class Teacher implements Serializable{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private String name; 
		private UserInfo student;
		
		public Teacher(String name,UserInfo student){
			this.name=name; 
			this.student= student; 
		}
		......  //get、set省略
	}

通過上面後面的程序代碼比較,可以看出T2和T3是同一個對象,T1的student引用的、T2的student引用的和user引用變量引用的是同一個對象。說明java序列化的機制如下圖:
這裏寫圖片描述

從上圖中我們可以看出:當我們多次調用writeObject 輸出同一個對象時,程序只有第一次調用writeObject方法纔會將該對象轉換成字節序列並輸出。
但這會照成另外一個問題:當程序序列化一個可變對象時,程序只有第一次使用writeObject()方法輸出時纔會將該對象轉換成字節序列並輸出,即使後面該對象的屬性已改變,改變的屬性值也不會被輸出,例如:

public class SerializeMutable {
	public static void main(String[] args) throws IOException{
		ObjectOutputStream out = null; 
		ObjectInputStream oin = null; 
		try {
			out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\mutable.txt"));
			UserInfo user = new UserInfo("pepper",25); 
			out.writeObject(user);
			user.setUserName("Tony");
			out.writeObject(user);
			
			// 反序列化
			oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\mutable.txt")); 
			UserInfo u1 = (UserInfo) oin.readObject(); 
			UserInfo u2 = (UserInfo) oin.readObject(); 
			
			System.out.println(u1 == u2);
			System.out.println(u2.getUserName());
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} 
		finally {
			if(out !=null){
				out.close();
			}
			if(oin !=null){
				oin.close();
			}
		}
	}
}

運行上面程序,程序比較讀取的Java對象完全相同,第二次讀取的UserInfo對象的userName 屬性依然是pepper,說明被改變的userInfo對象並沒有被寫入。

**靜態變量序列化 **

public class StaticSeriable  implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	//定義初始值爲5
	public static int staitcAcitivar = 5; 
	
	public static void main(String[] args){
		
		ObjectOutputStream out;
		try {
			out = new ObjectOutputStream(new FileOutputStream("result.obj"));
			out.writeObject(new StaticSeriable());
			out.close();
			//序列化後修改爲10
			StaticSeriable.staitcAcitivar = 10; 
			ObjectInputStream oIn = new ObjectInputStream(new FileInputStream("result.obj")); 
			StaticSeriable t = (StaticSeriable) oIn.readObject(); 
			oIn.close();
			//再讀取,通過t.staticActivar打印新值
			System.out.println(t.staitcAcitivar);
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} 
		
	}
}

上面程序將對象序列化後,修改靜態便令的值,再將對象從序列化對象讀取出來,打印對象的靜態變量值,是10而不是5,這是爲什麼呢?這是因爲序列化時,並不保存靜態變量,而是保存對象的狀態,靜態變量屬於類的狀態。

發佈了181 篇原創文章 · 獲贊 405 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章