Java 對象的序列化、反序列化


 

對象的序列化(Serialize):將內存中的Java對象轉換為與平台無關的二進制流(字節序列),然后存儲在磁盤文件中,或通過網絡傳輸給另一個網絡節點。

對象的反序列化(Deserialize):獲取序列化的二進制流(不管是通過網絡,還是通過讀取磁盤文件),將之恢復為原來的Java對象。

 

 

要實現對象的序列化,該對象所屬的類必須要是可序列化的,即該類必須實現以下2個接口之一:

  • Serializable     這只是一個標記接口,此接口只是表明該類是可序列化的,不用實現任何方法。Java自帶的類基本都已implement  Serializable,不用我們操心,我們自定義的類                         要實現序列化,必須要手寫implement  Serializable。
  • Externalizable      用於實現自定義序列化

 

要通過網絡傳輸的Java對象、要保存到磁盤的Java對象必須要是可序列化的,不然程序會出現異常。

JavaEE的分布式應用往往要跨平台、跨網絡,通過網絡傳輸的Java對象必須要是可序列化的,HttpSession、ServletContext等Java  Web對象都已實現序列化。如果我們要通過網絡傳輸自定義的Java對象,該類(一般是JavaBean)要是可序列化的。通常建議:把所有的JavaBean都寫成是可序列化的。

 

 

ObjectOutputStream是一個處理流,提供了void  writeObject(Object  obj)方法用於對象的序列化(對象輸出流,輸出到文件/網絡)。

ObjectInputStream也是一個處理流,提供了Object  readObject()方法用於反序列化(對象輸入流,讀取文件/網絡中的對象到內存)。

 

 

示例:

 1 //要實現Serializable接口(並不用實現任何方法)
 2 class Student implements Serializable{
 3     private int id;
 4     private String name;
 5     private int age;
 6 
 7     public Student(int id,String name,int age){
 8         this.id=id;
 9         this.name=name;
10         this.age=age;
11     }
12 
13    public int getId(){
14         return this.id;
15    }
16 
17    public void setId(int id){
18         this.id=id;
19    }
20 
21    public String getName(){
22         return this.name;
23    }
24 
25    public void setName(String name){
26         this.name=name;
27    }
28 
29    public int getAge(){
30         return this.age;
31    }
32 
33    public void setAge(int age){
34         this.age=age;
35    }

 

 1  //序列化
 2         ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));  //處理流,要建立在節點流之上
 3         Student zhangsan=new Student(1,"張三",20);
 4         oos.writeObject(zhangsan);   //writeObject(Object obj)用於序列化一個對象(輸出到文件/網絡節點)
 5 
 6         //反序列化
 7         ObjectInputStream ois=new ObjectInputStream(new FileInputStream("./obj.txt"));
 8         Student student=(Student)ois.readObject();   //返回的是Object類型,所以往往要強制類型轉換
 9 
10         System.out.println("學號:"+student.getId());
11         System.out.println("姓名:"+student.getName());
12         System.out.println("年齡:"+student.getAge());

 

ObjectInputStream、ObjectOutputStream也包含了其他IO方法,可輸入/輸出多種類型的數據,即可以字符為單位進行操作,又可以字節為單位進行操作。

 

 

writeObject(Object  obj)一次只能寫出一個對象,可多次調用,向同一文件中寫入多個對象;

readObject()一次只能讀一個對象,讀取一個對象后,指針自動后移,指向下一個對象。讀的順序和寫的順序是一一對應的。

示例:

 1  //序列化,向同一文件中寫入多個對象
 2         ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));  //處理流,要建立在節點流之上
 3         Student zhangsan=new Student(1,"張三",20);
 4         Student lisi=new Student(2,"李四",20);
 5         Student wangwu=new Student(3,"王五",20);
 6         oos.writeObject(zhangsan);  //張三
 7         oos.writeObject(lisi);  //李四
 8         oos.writeObject(wangwu);  //王五
 9 
10         //反序列化,讀取順序和寫入順序是一致的
11         ObjectInputStream ois=new ObjectInputStream(new FileInputStream("./obj.txt"));
12         Student zhangsan1=(Student)ois.readObject();    //張三
13         Student lisi1=(Student)ois.readObject();  //李四
14         Student wangwu1=(Student)ois.readObject();  //王五

 

 

 

 

如果要序列化的類有父類(直接或者間接),那么這些父類必須要是可序列化的,或者具有無參的構造函數,否則會拋出 InvalidClassException  異常。

1 class Student extends People implements Serializable{
2     //......
3 }

如果Student要序列化,則有2種方案可選:

  • 其父類People也要是可序列化的(遞歸)。       這樣Student繼承自People中的成員才可以序列化輸出到文件/網絡。
  • 其父類不是可序列化的,但是其父類有無參的構造函數。      這樣不會報錯,但Student繼承自People中的成員不會序列化輸出到文件/網絡。

 

 

 

 

如果該類持有其它類的引用,則引用的類都要是可序列化的,此類才是可序列化的。

1 class Student implements Serializable {
2     private int id;
3     private String name;  //Java自帶的類基本都實現了可序列化,不用管
4     private int age;
5     private Teacher teacher;    //此類持有一個Teacher類的引用。Teacher類必須要是可序列化的,Student類才是可序列化的。
6     //......
7 }

 

 

 

 

 

如果要多次輸出同一個對象,則第一次輸出的是該對象的字節序列,以后每次輸出的是該對象的編號。

1   ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
2         Student zhangsan=new Student(1,"張三",20);
3 
4         oos.writeObject(zhangsan);   //第一次序列化這個對象,輸出此對象的字節序列。假設此對象的編號為1,編號唯一標識此對象。
5         oos.writeObject(zhangsan);  //再次輸出此對象,輸出的是此對象的編號,直接輸出1。讀取此對象時讀取的仍是zhangsan這個對象。
6         oos.writeObject(zhangsan);  //直接輸出1。
7         //類似於c++中的指針,通過編號指向某個已存在對象。減少了重新序列化對象的時間、內存開銷。

 

如果中間修改了此對象,再次序列化此對象時,修改后的結果不會同步到文件/網絡節點中。

 1  ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
 2         Student zhangsan=new Student(1,"張三",20);
 3 
 4         oos.writeObject(zhangsan);   //初次序列化,輸出此對象的字節序列。假設編號為1
 5         zhangsan.setName("李四");   //之后修改了此對象
 6         oos.writeObject(zhangsan);  //再次輸出此對象,輸出的是編號1。1代表編號為1的對象。
 7 
 8         /*
 9         在此對象第一次序列化之后,對此對象做修改,后面再次輸出此對象時,都是指向第一次輸出的那個對象,做的修改並不會寫到文件/網絡節點中。
10         讀取的也都是第一次輸出的那個對象。
11 
12         實際上,序列化一個對象時,JVM會先檢查在本次虛擬機中是否已序列化過這個對象,如果已序列化過,直接輸出對應的序列化編號,未序列化過才序列化。
13      JVM是以對象名來區分序列化的是否是同一個對象。對象名相同,JVM就認為序列化的是同一個對象,直接輸出該對象的編號,並不判斷此對象是否修改過。
14

15 修改此對象后,就算我們把此對象序列化到另一個文件中,輸出的也是此對象第一次序列化時的編號,修改並不會同步到這個文件中。
16 */

 

 

 

 

 

Java9新增了過濾功能,讀取對象時,可以先檢查該對象是否滿足指定的要求,滿足才允許恢復,否則拒絕恢復。

 1  ObjectInputStream ois=new ObjectInputStream(new FileInputStream("obj.txt"));
 2         /*
 3         參數是一個ObjectInputFilter接口的對象,函數式接口,只需實現checkInput()方法。
 4         參數info是FilterInfo類的對象。
 5 
 6         返回值要是預定義的常量:
 7         Status.ALLOWED  允許恢復
 8         Status.REJECTED  拒絕恢復
 9         Status.UNDECIDED  未決定,將繼續執行檢查
10          */
11         ois.setObjectInputFilter((info)->{
12             ObjectInputFilter serialFilter=ObjectInputFilter.Config.getSerialFilter();   //獲取默認的輸入過濾器
13             if(serialFilter!=null){
14                 //先使用默認的過濾器進行檢查
15                 ObjectInputFilter.Status status=serialFilter.checkInput(info);
16                 //如果已知道結果
17                 if (status!= ObjectInputFilter.Status.UNDECIDED)
18                     return status;
19             }
20             //執行到此,就說明結果是Status.UNDECIDED,還不知道結果,要繼續檢查
21             //如果引用不唯一,說明數據被污染了,不允許恢復
22             if(info.references()!=1)
23                 return ObjectInputFilter.Status.REJECTED;
24             //是指定的類的對象(Student),才允許恢復(執行反序列化)
25             if(info.serialClass()!=null && info.serialClass() != Student.class)
26                 return ObjectInputFilter.Status.ALLOWED;
27             //執行到此,說明不是指定類的對象,不允許恢復。
28             return ObjectInputFilter.Status.REJECTED;
29         });
30         //這樣恢復的對象都是唯一的Student對象
31         //...........

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM