【Java】Java物件的序列化和反序列化
物件序列化
物件序列化機制允許把記憶體中的Java物件轉換成與平臺無關的二進位制流,從而可以儲存到磁碟或者進行網路傳輸,其它程式獲得這個二進位制流後可以將其恢復成原來的Java物件。 序列化機制可以使物件可以脫離程式的執行而對立存在
序列化的含義和意義
序列化
序列化機制可以使物件可以脫離程式的執行而對立存在
序列化(Serialize)指將一個java物件寫入IO流中,與此對應的是,物件的反序列化(Deserialize)則指從IO流中恢復該java物件
如果需要讓某個物件可以支援序列化機制,必須讓它的類是可序列化(serializable),為了讓某個類可序列化的,必須實現如下兩個介面之一:
-
- Serializable:標記介面,實現該介面無須實現任何方法,只是表明該類的例項是可序列化的
- Externalizable
所有在網路上傳輸的物件都應該是可序列化的,否則將會出現異常;所有需要儲存到磁盤裡的物件的類都必須可序列化;程式建立的每個JavaBean類都實現Serializable;
類自定義序列化方式一:實現Serializable介面
序列化
示例程式碼
1 /** 2 * ObjectInputStream和OjbectOutputSteam 3 * 用於儲存和讀取基本資料型別資料或物件的處理流。它的強大之處就是可 4 * 以把Java中的物件寫入到資料來源中,也能把物件從資料來源中還原回來。5 */ 6 public class ObjectInputOutputStreamTest { 7 public static void main(String[] args) { 8 ObjectInputOutputStreamTest test = new ObjectInputOutputStreamTest(); 9 test.testObjectOutputStream(); 10 } 11 12 /** 13 * 序列化:用ObjectOutputStream類儲存基本型別資料或物件的機制 14 */15 public void testObjectOutputStream(){ 16 ObjectOutputStream oos = null; 17 try { 18 oos = new ObjectOutputStream(new FileOutputStream("object.bat")); 19 oos.writeObject(new Person(12, "小白")); 20 oos.flush(); 21 } catch (IOException e) { 22 e.printStackTrace(); 23 } finally { 24 if(oos != null) { 25 try { 26 oos.close(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 } 32 } 33 34 } 35 36 37 class Person implements Serializable{ 38 39 public static final long serialVersionUID = 123321; 40 41 Integer age; 42 String name; 43 44 public Person(Integer age, String name) { 45 this.age = age; 46 this.name = name; 47 } 48 49 @Override 50 public String toString() { 51 return "Person{" + 52 "age=" + age + 53 ", name='" + name + '\'' + 54 '}'; 55 } 56 }
反序列化
從二進位制流中恢復Java物件,則需要使用反序列化,程式可以通過如下兩個步驟來序列化該物件:
示例程式碼
1 /** 2 * 反序列化:用ObjectInputStream類讀取基本型別資料或物件的機制 3 */ 4 public void testObjectInputStream(){ 5 ObjectInputStream ois = null; 6 try { 7 ois = new ObjectInputStream(new FileInputStream("object.bat")); 8 Person p = (Person) ois.readObject(); 9 System.out.println(p); 10 } catch (IOException e) { 11 e.printStackTrace(); 12 } catch (ClassNotFoundException e) { 13 e.printStackTrace(); 14 } finally { 15 if(ois != null) { 16 try { 17 ois.close(); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 } 23 }
反序列化讀取的僅僅是Java物件的資料,而不是Java類,因此採用反序列化恢復Java物件時,必須提供Java物件所屬的class檔案,否則會引發ClassNotFoundException異常;反序列化機制無須通過構造器來初始化Java物件
如果使用序列化機制向檔案中寫入了多個Java物件,使用反序列化機制恢復物件必須按照實際寫入的順序讀取。當一個可序列化類有多個父類時(包括直接父類和間接父類),這些父類要麼有無參的構造器,要麼也是可序列化的—否則反序列化將丟擲InvalidClassException異常。如果父類是不可序列化的,只是帶有無引數的構造器,則該父類定義的Field值不會被序列化到二進位制流中
物件引用的序列化
如果某個類的Field型別不是基本型別或者String型別,而是另一個引用型別,那麼這個引用型別必須是可序列化的,否則有用該型別的Field的類也是不可序列化的
serialVersionUID
1、凡是實現Serializable介面的類都有一個表示序列化版本識別符號的靜態變數:
private static final long serialVersionUID;
serialVersionUID用來表明類的不同版本間的相容性。簡言之,其目的是以序列化物件 進行版本控制,有關各版本反序列化時是否相容。
如果類沒有顯示定義這個靜態常量,它的值是Java執行時環境根據類的內部細節自 動生成的。若類的例項變數做了修改,serialVersionUID可能發生變化。故建議, 顯式宣告。
2、簡單來說,Java的序列化機制是通過在執行時判斷類的serialVersionUID來驗 證版本一致性的。在進行反序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同 就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異 常。(InvalidCastException)
Java序列化的特殊情況
1:靜態變數和transient關鍵字修飾的變數不能被序列化;
2:反序列化時要按照序列化的順序重構物件:如先序列化A後序列化B,則反序列化時也要先獲取A後獲取B,否則報錯。
3:序列化ID的作用:虛擬機器是否允許物件反序列化,不僅取決於該物件所屬類路徑和功能程式碼是否與虛擬機器載入的類一致,而是主要取決於物件所屬類與虛擬機器載入的該類的序列化 ID 是否一致。
4:自定義序列化方法的應用場景:對某些敏感資料進行加密操作後再序列化;反序列化對加密資料進行解密操作。
5:重複序列化:同一個物件重複序列化時,不會把物件內容再次序列化,而是新增一個引用指向第一次序列化時的物件而已。
類自定義序列化方式二:實現Externalnalizable介面
實現Externalnalizable介面(繼承自 Serializable介面),並且在類中實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,在方法中定義類物件自定義的序列化和反序列化操作。這樣通過物件輸出流和物件輸入流的輸入輸出方法序列化和反序列化物件時會自動呼叫類中定義的readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法。
void readExternal(ObjectInput in):需要序列化的類實現readExternal()方法來實現反序列化。該方法呼叫DataInput(它是ObjectInput的父介面)的方法來恢復基本型別的例項變數值,呼叫ObjectInput的readObject()方法來恢復引用型別的例項變數值
void writeExternal(Object out):需要序列化的類實現該方法來儲存物件的狀態。該方法呼叫DataOutput(它是ObjectOutput的父介面)的方法來儲存基本型別的例項變數值,呼叫ObjectOutput的writeObject()方法來儲存引用型別的例項變數值
示例程式碼
1 public class ObjectInputOutputStreamTest { 2 public static void main(String[] args) { 3 ObjectInputOutputStreamTest test = new ObjectInputOutputStreamTest(); 4 test.testAnimal(); 5 } 6 7 8 public void testAnimal(){ 9 ObjectOutputStream oos = null; 10 try { 11 oos = new ObjectOutputStream(new FileOutputStream("object2.bat")); 12 oos.writeObject(new Animal(12, "小白")); 13 oos.flush(); 14 } catch (IOException e) { 15 e.printStackTrace(); 16 } finally { 17 if(oos != null) { 18 try { 19 oos.close(); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 } 25 26 27 ObjectInputStream ois = null; 28 try { 29 ois = new ObjectInputStream(new FileInputStream("object2.bat")); 30 Animal a = (Animal) ois.readObject(); 31 System.out.println(a); 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } catch (ClassNotFoundException e) { 35 e.printStackTrace(); 36 } finally { 37 if(ois != null) { 38 try { 39 ois.close(); 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } 43 } 44 } 45 } 46 } 47 48 49 50 class Animal implements Externalizable{ 51 52 public static final long serialVersionUID = 12345; 53 54 Integer age; 55 String name; 56 57 public Animal() { 58 } 59 60 public Animal(Integer age, String name) { 61 this.age = age; 62 this.name = name; 63 } 64 65 public void writeExternal(ObjectOutput out) throws IOException { 66 out.writeInt(age); 67 out.writeUTF(name); 68 } 69 70 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 71 age = in.readInt(); 72 name = in.readUTF(); 73 } 74 75 76 @Override 77 public String toString() { 78 return "Animal{" + 79 "age=" + age + 80 ", name='" + name + '\'' + 81 '}'; 82 } 83 }View Code
其他序列化手段
1:把物件包裝成JSON格式進行序列化
2:用XML格式序列化
3:採用第三方外掛(如:ProtoBuf)進行序列化