Java提供的序列化和反序列化
序列化:是指將Java對象轉換為二進制數據。
反序列化:將二進制數據轉換為Java對象。
與序列化功能相關的類有:
- java.io.Serializable;
- java.io.ObjectOutputStream(用於序列化)
- java.io.ObjectInputStream(用於反序列化)
序列化對象的前提:
- 該對象所屬的類實現了 java.io.Serializable 接口
- 該類的成員變量中有一個是序列化id
反序列化對象的前提:
- 反序列化對象類也需要實現 java.io.Serializable 接口
序列化端和反序列化端,序列化對象類和反序列化對象類
- 兩者的類名,包名需要保持一致。否則反序列化時會拋出java.lang.ClassCastException異常。
- 兩者的序列化id需要保持一致。否則反序列化時會拋出java.io.InvalidClassException異常。
- 兩者中的成員變量名保持一致。
當然,反序列化對象類可以包含額外的成員變量,也可以不包含序列化對象類中的成員變量,只不過這樣就無法讀取到該成員變量的值。
序列化對象機制的特點
- 序列化保存的是對象的狀態,靜態變量屬於類的狀態,因此 序列化並不保存靜態變量。
- 如果想父類對象也序列化,就需要讓父類也實現 Serializable 接口。
- 實現 Serializable 接口的類,Array,enum 都能能被序列化。
序列化對象加密傳輸
服務器端給客戶端發送序列化對象數據,對象中有一些數據是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時,才可以對密碼進行讀取,這樣可以一定程度保證序列化對象的數據安全。
Java序列化提供的解決方案:
在序列化過程中,虛擬機會試圖調用對象類裏的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。
示例:
項目A:序列化對象類:
package com.java.serializable; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.ObjectOutputStream; import java.io.ObjectOutputStream.PutField; import java.io.Serializable; public class Class03 implements Serializable { // 序列化 ID private static final long serialVersionUID = 1L; // 序列化時不加密 private String name; // 序列化時加密 private String password="initValue"; // 測試temp是否也能被自動序列化 private String temp = "test value of temp"; // 以下省略setter、getter方法 private void writeObject(ObjectOutputStream oos) { try { PutField fields = oos.putFields(); fields.put("password", encrypt(this.password)); fields.put("name", this.name); oos.writeFields(); } catch (Exception e) { e.printStackTrace(); } } // 將參數加密 private String encrypt(String pwd) { return "encryptValue"; } }View Code
項目A:序列化對象工具類
package com.java.serializable; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class CtestA03 { public static void main(String[] args) { serializeObjectToFile(); } // 存放Java對象二進制數據的文件 private static final String PATH = "F:\\objFile.txt"; // 將Java對象序列化為二進制數據存儲到文件objFile.txt private static void serializeObjectToFile() { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(PATH)); Class03 classObj = new Class03(); classObj.setName("Class03.name"); oos.writeObject(classObj); } catch (Exception e) { e.printStackTrace(); } finally { if(null != oos) { try { oos.close(); } catch (IOException e) {} } } } }View Code
項目B:序列化對象類
package com.java.serializable; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.ObjectOutputStream; import java.io.ObjectOutputStream.PutField; import java.io.Serializable; public class Class03 implements Serializable { // 序列化 ID private static final long serialVersionUID = 1L; // 昵稱:序列化時不加密 private String name; // 反序列化時需要解密 private String password="initValue"; // 測試temp是否能通過反序列化讀取到值 private String temp; // 以下省略setter、getter方法 private void readObject(ObjectInputStream ois) { try { GetField fields = ois.readFields(); String encryptedVar = (String) fields.get("password", ""); this.password = decrypt(encryptedVar); this.name = (String) fields.get("name", ""); } catch (Exception e) { e.printStackTrace(); } } // 解密參數 private String decrypt(String pwd) { return "initValue-decrypted"; } }View Code
項目B:反序列化工具類
package com.java.serializable; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class CtestB03 { public static void main(String[] args) { reverseSerializeFileToObject(); } private static final String PATH = "F:\\objFile.txt"; // 反序列化 private static void reverseSerializeFileToObject() { ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(PATH)); Class03 classObj = (Class03) ois.readObject(); System.out.println("classObj.name="+classObj.getName());// classObj.name=Class03.name System.out.println("classObj.password="+classObj.getPassword());// classObj.password=initValue-decrypted System.out.println("classObj.temp="+classObj.getTemp());// classObj.temp=null } catch (Exception e) { e.printStackTrace(); } finally { if(null != ois) { try { ois.close(); } catch (IOException e) {} } } } }View Code
執行main方法的結果如下:
System.out.println("classObj.name="+classObj.getName());// classObj.name=Class03.name
未加密的成員變量name,反序列化後得到的仍是序列化之前的值。
System.out.println("classObj.temp="+classObj.getTemp());// classObj.temp=null
序列化對象的成員變量temp,執行writeObject()時,沒有將該變量添加到fields中,所以沒有被序列化,反序列化後得到的值為null。
System.out.println("classObj.password="+classObj.getPassword());// classObj.password=initValue-decrypted
加密後的成員變量password,會先解密。最後讀到的是解密後的密碼值initValue-decrypted。
禁止序列化對象的成員變量
transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中。
在被反序列化後,transient修飾的變量的值為初始值,如 int 型的是 0,對象型的是 null。
示例:
項目A:序列化對象類
package com.java.serializable; import java.io.Serializable; public class Class04 implements Serializable { // 序列化ID private static final long serialVersionUID = 1L; // 昵稱 private String nickName; // 關鍵字transient修飾,該變量無法被序列化 private transient int age = 26; // 關鍵字transient修飾,該變量無法被序列化 private transient String sex = "man"; // 以下省略setter、getter方法 }View Code
項目A:序列化對象工具類
package com.java.serializable; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class CtestA04 { public static void main(String[] args) { serializeObjectToFile(); } private static final String PATH = "F:\\objFile.txt"; // 序列化對象,轉換成二進制數據存儲到文件objFile.txt private static void serializeObjectToFile() { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(PATH)); Class04 classObj = new Class04(); classObj.setNickName("nickName"); oos.writeObject(classObj); } catch (Exception e) { e.printStackTrace(); } finally { if(null != oos) { try { oos.close(); } catch (IOException e) {} } } } }View Code
項目B:反序列化對象
package com.java.serializable; import java.io.Serializable; public class Class04 implements Serializable { private static final long serialVersionUID = 1L; private String nickName; private int age; private String sex; // 以下省略setter、getter方法 }View Code
項目B:反序列化對象工具類
package com.java.serializable; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class CtestB04 { public static void main(String[] args) { reverseSerializeFileToObject(); } // 存儲Java對象二進制數據的文件 private static final String PATH = "F:\\objFile.txt"; /** * 將二進制文件反序列化為java的object對象 * * 反序列化條件: * 1.java類的包名一致 * 2.java類中變量名,變量類型一致 * 3.序列化ID一致 */ private static void reverseSerializeFileToObject() { ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(PATH)); Class04 classObj = (Class04) ois.readObject(); System.out.println("classObj.nickName="+classObj.getNickName()); System.out.println("classObj.age="+classObj.getAge()); System.out.println("classObj.sex="+classObj.getSex()); } catch (Exception e) { e.printStackTrace(); } finally { if(null != ois) { try { ois.close(); } catch (IOException e) {} } } } }View Code
執行結果如下:
System.out.println("classObj.nickName="+classObj.getNickName());// classObj.nickName=nickName;
成員變量nickName,可以正常讀取值。
System.out.println("classObj.age="+classObj.getAge());// classObj.age=0 System.out.println("classObj.sex="+classObj.getSex());// classObj.sex=null
使用transient 關鍵字修飾的成員變量age和sex,值為null,說明這兩個變量並沒有被序列化到二進制文件中。
序列化對象的存儲機制
Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。
示例:
項目A:序列化對象類
package com.java.serializable; import java.io.Serializable; public class Class05 implements Serializable { private static final long serialVersionUID = 1L; private String name; // 以下省略setter、getter方法 }View Code
項目A:序列化對象工具類
package com.java.serializable; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class CtestA05 { public static void main(String[] args) { serializeObjectToFile(); } private static final String PATH = "D:\\objFile.txt"; // 將 Java 對象序列化到文件objFile.txt中 private static void serializeObjectToFile() { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(PATH)); // 將對象兩次寫入文件 Class05 classObj = new Class05(); classObj.setName("classObj.name.1"); oos.writeObject(classObj); oos.flush(); classObj.setName("classObj.name.2"); oos.writeObject(classObj); } catch (Exception e) { e.printStackTrace(); } finally { if(null != oos) { try { oos.close(); } catch (IOException e) {} } } } }View Code
項目B:反序列化對象
package com.java.serializable; import java.io.Serializable; public class Class05 implements Serializable { private static final long serialVersionUID = 1L; private String name; // 以下省略setter、getter方法 }View Code
項目B:反序列化對象工具類
package com.java.serializable; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class CtestB05 { public static void main(String[] args) { reverseSerializeFileToObject(); } private static final String PATH = "D:\\objFile.txt"; // 將二進制文件反序列化為java的object對象 private static void reverseSerializeFileToObject() { ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(PATH)); // 從文件依次讀出兩個文件 Class05 classObj1 = (Class05) ois.readObject(); Class05 classObj2 = (Class05) ois.readObject(); /** * Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。 * 反序列化時,恢復引用關系,使得classObj1 和 classObj2 指向唯一的對象,二者相等,輸出 true。該存儲規則極大的節省了存儲空間。 */ System.out.println("classObj1 == classObj2 : "+(classObj1 == classObj2)); System.out.println("classObj1.name="+classObj1.getName()); System.out.println("classObj2.name="+classObj2.getName()); } catch (Exception e) { e.printStackTrace(); } finally { if(null != ois) { try { ois.close(); } catch (IOException e) {} } } } }View Code
輸出結果如下:
System.out.println("classObj1 == classObj2 : "+(classObj1 == classObj2));// classObj1 == classObj2 : true System.out.println("classObj1.name="+classObj1.getName());// classObj1.name=classObj.name.1 System.out.println("classObj2.name="+classObj2.getName());// classObj1.name=classObj.name.1
Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。反序列化時,恢復引用關系,使得classObj1 和 classObj2 指向唯一的對象,二者相等,輸出 true。該存儲規則極大的節省了存儲空間。
Java提供的序列化和反序列化