天天看點

java的serializable序列化引言前提條件序列化id的問題靜态化變量序列化Transient關鍵字父類的序列化定制序列化序列化存儲規則

引言

java序列化就是java類實作Serializable接口,然後利用ObjectOutputStream将java對象寫到二進制檔案中,然後将該二進制檔案通過網絡傳輸到其它java虛拟機環境中,再利用ObjectInputStream将該二進制檔案還原為java對象。本質上是将java虛拟機中生成的對象轉移到其它java虛拟機中使用。

前提條件

情境:兩個用戶端 A 和 B 試圖通過網絡傳遞對象資料,A 端将對象 C 序列化為二進制資料再傳給 B,B 反序列化得到 C。那麼要想序列化和反序列化成功需要具備以下條件: 1.A端和B端都要有C類,而且類路徑和功能代碼一緻。 2.C類實作Serailizable接口 3.C類的序列化id一緻。

序列化id的問題

java序列化機制是通過在運作時判斷類的序列化id(serialVersionUID)來驗證版本一緻性的。在進行反序列化時,jvm會把傳來的位元組流中的serialVersionUID與本地相應java類的serialVersionUID進行比較,如果相同就認為是一緻的,可以進行反序列化,否則就會出現序列化版本不緻的異常(InvalidClassException )。 1.不顯示設定id jdk文檔中有解釋,建議我們顯示聲明,因為如果不聲明,Java序列化機制會根據編譯的class自動生成一個serialVersionUID,這種情況下,隻有同一次編譯生成的class才會生成相同的serialVersionUID,這樣的話很容易導緻序列化版本不一緻的異常。 2.顯示設定id 在eclipse中顯示設定序列化id有兩種方式: 1.add default serial version ID: 生成1L,對于這一種,需要了解哪些情況是相容的,哪些是不相容的,在可相容的前提下,可以保留舊版本号,如果不相容,或者想讓它不相容,就手工遞增版本号。1》2》3 2.add generated serial version ID: 生成一個很大的數,這種方式是根據類的結構産生的hash值,增減屬性、方法等,都可能會導緻這個值産生變化。如果開發人員認為每次修改類後都需要生成新的版本号,不想向下相容,操作就是删除原有的SerialVersionUid聲明語句,再自動生成一下。

靜态化變量序列化

java在序列化時,并不儲存靜态變量資訊,這其實比較容易了解,序列化儲存的是對象的狀态,靜态變量屬于類的狀态。

Transient關鍵字

Transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到檔案中,在被反序列化後,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。

父類的序列化

情境:一個子類實作了Serializable接口,它的父類沒有實作,序列化該子類對象,然後反序列化輸出父類定義的變量的數值,該變量數值跟Transient關鍵字的效果一樣,都是初始值。 解決:要想将父類對象的屬性也序列化,就需要讓父類也實作Serializalbe接口。 如果父類不實作序列化,就需要有預設的無參的構造函數。在父類沒有實作Serializable接口時,虛拟機不會序列化父類對象,而一個java對象的構造必須先有父類對象,反序列化時也不例外。是以反序列化時,為了構造父對象,隻能調用父類的無參構造函數作為預設的父對象。如果你考慮到這種序列化的情況,在父類的無參構造函數中對變量進行初始化,否則的話,父類變量值都是預設聲明的值,如 int 型的是 0,對象型的是 null。 結論:根據以上, Transient 關鍵字可以使得字段不被序列化,那麼還有别的方法嗎?根據父類對象序列化的規則,我們可以将不需要被序列化的字段抽取出來放到父類中,子類實作 Serializable 接口,父類不實作,根據父類序列化規則,父類的字段資料将不被序列化。

定制序列化

原理:在序列化過程中,虛拟機會試圖調用對象類裡面的writeObject(private)和readObject(private)方法,進行使用者自定義的序列化和反序列化,如果沒有定義該方法,則預設調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。使用者自定義的 writeObject 和 readObject 方法可以允許使用者控制序列化的過程,當然使用者自定義了writeObject也可以調用defaultWriteObject來處理預設的序列化方法。 關于對象裡面的writeObject和readObject必須是private類型的,這個嗎,ObjectOutputStream使用了反射來尋找是否聲明了這兩個方法,因為ObjectOutputStream使用getPrivateMethod,是以這些方法不得不被聲明為priate以至于供ObjectOutputStream來使用。 定制序列化的過程有兩種試,一種是key,value型,一種是value型。 第一種: 序列化:key是對象中必須存在的字段的名稱,value是指定的值。

private void writeObject(ObjectOutputStream oos) {
	try {
		PutField putField = oos.putFields();
		putField.put("name", "bao");
		putField.put("name2", "elva");
		oos.writeFields();
	} catch (IOException e) {
		e.printStackTrace();
	}
}
           

反序列化:key是寫的時候的值,不一定是類中必須存在的

private void readObject(ObjectInputStream ois) {
	try{
		GetField getField = ois.readFields();
		String name = (String)getField.get("name", "");
		System.out.println(name);
		String name2 = (String)getField.get("name2", "");
		System.out.println(name2);
	} catch (IOException e) {
		e.printStackTrace();
	} catch (ClassNotFoundException e) {
		e.printStackTrace();
	}
}
           

第二種: 序列化:依次寫入不同字段的值,這裡隻有值,沒有字段名(key)

private void writeObject(ObjectOutputStream oos) {
		try {
			oos.defaultWriteObject(); // 執行預設的序列化過程
			oos.writeObject("aaaaaaa"); // 添加值aaaaaa
			oos.writeObject("bbbbbbb");// 添加值bbbbbb
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
           

反序列化:依昭序列化時的順序和個數擷取相應的字段值

private void readObject(ObjectInputStream ois) {
		try{
			ois.defaultReadObject();
			Object obj1 = ois.readObject();
			Object obj2 = ois.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
           

使用場景:如果序列化的對象資料中,有些資料是敏感的,比如密碼字元串等,希望對密碼字段在序列化時,進行加密,在反序列化時,對密碼字段進行解密,這樣一定程式上保證序列化對象的資料安全。

序列化存儲規則

原理:當序列化同一個對象多次時,序列化存儲的二進制檔案中該對象隻存一份,剩下的全部存引用,這樣可以減少存儲空間。如果序列化同一個對象多次,當反序列化時獲得的對象是同一個,都指向同一個内在位址。 序列化同一個對象多次時,存儲的二進制檔案空間并沒有增加2倍,而隻增加一點點(存儲對象位址的那部分空間):

public static void main(String[] args) throws Exception {
		
		FileOutputStream fos = new FileOutputStream(new File("D:/serializable.txt"));
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		SerializableEntity entity = new SerializableEntity("elva", "29", "151********");
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 126
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 131
		
		oos.close();
		fos.close();
		
	}
           

反序列化時:

public static void main(String[] args) throws Exception {
		
		FileInputStream fis = new FileInputStream(new File("D:/serializable.txt"));
		ObjectInputStream ois = new ObjectInputStream(fis);
		
		SerializableEntity entity = (SerializableEntity)ois.readObject();
		SerializableEntity entity2 = (SerializableEntity)ois.readObject();
		
		System.out.println(entity == entity2); // true
		
		ois.close();
		fis.close();
		
	}
           

如果序列化多次同一個對象的過程中修改了該對象的某個字段值,那麼反序列化時擷取的對象的值保持不變:因為序列化多次同一個對象隻會序列化一次對象資料,剩下的是引用。

public static void main(String[] args) throws Exception {
		
		FileOutputStream fos = new FileOutputStream(new File("D:/serializable.txt"));
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		SerializableEntity entity = new SerializableEntity("elva", "29", "151********");
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 126
		entity.setName("bao");
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 131
		
		oos.close();
		fos.close();
		
	}
           
public static void main(String[] args) throws Exception {
		
		FileInputStream fis = new FileInputStream(new File("D:/serializable.txt"));
		ObjectInputStream ois = new ObjectInputStream(fis);
		
		SerializableEntity entity = (SerializableEntity)ois.readObject();
		System.out.println(entity.getName()); // elva
		SerializableEntity entity2 = (SerializableEntity)ois.readObject();
		System.out.println(entity.getName()); // elva
		
		System.out.println(entity == entity2); // true
		
		ois.close();
		fis.close();
		
	}
           

參考: http://blog.csdn.net/jiangwei0910410003/article/details/18989711/ http://blog.csdn.net/mashangyou/article/details/21827613