(JAVA)從零開始之--對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)


 

對象的輸入輸出流 : 主要的作用是用於寫入對象信息與讀取對象信息。 對象信息一旦寫到文件上那么對象的信息就可以做到持久化了
  對象的輸出流ObjectOutputStream
  對象的輸入流:  ObjectInputStream

使用:

對象的輸出流將指定的對象寫入到文件的過程,就是將對象序列化的過程,對象的輸入流將指定序列化好的文件讀出來的過程,就是對象反序列化的過程。既然對象的輸出流將對象寫入到文件中稱之為對象的序列化,那么可想而知對象所對應的class必須要實現Serializable接口。(查看源碼可得知:Serializable接口沒有任何的方法,只是作為一個標識接口存在)。

1、將User類的對象序列化

 1 class User implements Serializable{//必須實現Serializable接口
 2     String uid;
 3     String pwd;
 4     public User(String _uid,String _pwd){
 5         this.uid = _uid;
 6         this.pwd = _pwd;
 7     }
 8     @Override
 9     public String toString() {
10         return "賬號:"+this.uid+" 密碼:"+this.pwd;
11     }
12 }
13 
14 public class Demo1 {
15 
16     public static void main(String[] args) throws IOException {
17         //假設將對象信息寫入到obj.txt文件中,事先已經在硬盤中建立了一個obj.txt文件
18         File f = new File("F:\\obj.txt");
19         writeObjec(f);
20         System.out.println("OK");
21     }
22     
23     //定義方法把對象的信息寫到硬盤上------>對象的序列化。
24     public static void writeObjec(File f) throws IOException{
25         FileOutputStream outputStream = new FileOutputStream(f);//創建文件字節輸出流對象
26         ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
27         objectOutputStream.writeObject(new User("酒香逢","123"));
28         //最后記得關閉資源,objectOutputStream.close()內部已經將outputStream對象資源釋放了,所以只需要關閉objectOutputStream即可
29         objectOutputStream.close();
30     }
31 }

運行程序得到記事本中存入的信息:可見已經序列化到記事本中

2、將序列化到記事本的內容反序列化

 1 public class Demo1 {
 2 
 3     public static void main(String[] args) throws IOException, ClassNotFoundException {
 4         //假設將對象信息寫入到obj.txt文件中,事先已經在硬盤中建立了一個obj.txt文件
 5         File f = new File("F:\\obj.txt");
 6         //writeObjec(f);
 7         readObject(f);
 8         System.out.println("OK");
 9     }
10     
11     //定義方法把對象的信息寫到硬盤上------>對象的序列化。
12     public static void writeObjec(File f) throws IOException{
13         FileOutputStream outputStream = new FileOutputStream(f);//創建文件字節輸出流對象
14         ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
15         objectOutputStream.writeObject(new User("酒香逢","123"));
16         //最后記得關閉資源,objectOutputStream.close()內部已經將outputStream對象資源釋放了,所以只需要關閉objectOutputStream即可
17         objectOutputStream.close();
18     }
19     //把文件中的對象信息讀取出來-------->對象的反序列化
20     public static void readObject(File f) throws IOException, ClassNotFoundException{
21         FileInputStream inputStream = new FileInputStream(f);//創建文件字節輸出流對象
22         ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
23         User user = (User)objectInputStream.readObject();
24         System.out.println(user);
25     }
26 }

運行代碼得到的結果:

賬號:酒香逢 密碼:123
OK

但是,如果這時候這個obj.txt是我們項目中一個文件,而項目到后期在原來User類的基礎上添加成員變量String userName;

 1 class User implements Serializable{//必須實現Serializable接口
 2     String uid;
 3     String pwd;
 4     String userName="名字";//新添加的成員變量
 5     public User(String _uid,String _pwd){
 6         this.uid = _uid;
 7         this.pwd = _pwd;
 8     }
 9     @Override
10     public String toString() {
11         return "賬號:"+this.uid+" 密碼:"+this.pwd;
12     }
13 }

 

這時候如果我們再反序列化,則會引發下面的異常:

 

Exception in thread "main" java.io.InvalidClassException: xuliehua.User; local class incompatible: stream classdesc serialVersionUID = 2161776237447595412, local class serialVersionUID = -3634244984882257127
  at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:604)
  at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601)
  at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
  at xuliehua.Demo1.readObject(Demo1.java:48)
  at xuliehua.Demo1.main(Demo1.java:32)

異常信息解讀:

serialVersionUID 是用於記錄class文件的版本信息的,serialVersionUID這個數字是JVM(JAVA虛擬界)通過一個類的類名、成員、包名、工程名算出的一個數字。而這時候序列化文件中記錄的serialVersionUID與項目中的不一致,即找不到對應的類來反序列化。

3、如果序列化與反序列化的時候可能會修改類的成員,那么最好一開始就給這個類指定一個serialVersionUID,如果一類已經指定的serialVersionUID,然后
在序列化與反序列化的時候,jvm都不會再自己算這個 class的serialVersionUID了。

去掉剛才添加的成員變量userName;,並且在User類中指定一個serialVersionUID 

 1 class User implements Serializable{//必須實現Serializable接口
 2     
 3     private static final long serialVersionUID = 1L;
 4     String uid;
 5     String pwd;
 6     //String userName="名字";//新添加的成員變量
 7     public User(String _uid,String _pwd){
 8         this.uid = _uid;
 9         this.pwd = _pwd;
10     }
11     @Override
12     public String toString() {
13         return "賬號:"+this.uid+" 密碼:"+this.pwd;
14     }
15 }

重新序列化到obj.txt文件中,然后再類中再將userName添加回來(將上面User類中userName字段解注釋),再一次執行反序列化操作,執行的結果跟之前反序列化的結果是一致的。可見這樣解決后我們后期修改類也是可行的。

4、如果在User類中再添加成員變量,而這個變量為一個class ,如Address,那么Address類也必須要實現Serializable接口。

 1 class Address implements Serializable{
 2     String country;
 3     String city;
 4 }
 5 
 6 class User implements Serializable{//必須實現Serializable接口
 7     
 8     private static final long serialVersionUID = 1L;
 9     String uid;
10     String pwd;
11     String userName="名字";//新添加的成員變量
12     Address address;//成員變量為Address
13     public User(String _uid,String _pwd){
14         this.uid = _uid;
15         this.pwd = _pwd;
16     }
17     @Override
18     public String toString() {
19         return "賬號:"+this.uid+" 密碼:"+this.pwd;
20     }
21 }

5、最后再提一下關鍵字transient關鍵字,當你不想要某些字段序列化時候,可以用transient關鍵字修飾

 1 class User implements Serializable{//必須實現Serializable接口
 2     
 3     private static final long serialVersionUID = 1L;
 4     String uid;
 5     String pwd;
 6     transient String userName="名字";//新添加的成員變量//添加關鍵字transient后,序列化時忽略
 7     Address address;//成員變量為Address
 8     public User(String _uid,String _pwd){
 9         this.uid = _uid;
10         this.pwd = _pwd;
11     }
12     @Override
13     public String toString() {
14         return "賬號:"+this.uid+" 密碼:"+this.pwd;
15     }
16 }

最后總結一下對象輸入輸出流使用時需要注意:

1. 如果對象需要被寫出到文件上,那么對象所屬的類必須要實現Serializable接口。 Serializable接口沒有任何的方法,是一個標識接口而已。
2. 對象的反序列化創建對象的時候並不會調用到構造方法的、(這點文中沒有說到,想要驗證的同學在構造方法后面加一句System.out.println("構造方法執行嗎?");,實際上構造方法是不執行的,自然這句話也沒有輸出了)
3. serialVersionUID 是用於記錄class文件的版本信息的,serialVersionUID這個數字是通過一個類的類名、成員、包名、工程名算出的一個數字。
4. 使用ObjectInputStream反序列化的時候,ObjeectInputStream會先讀取文件中的serialVersionUID,然后與本地的class文件的serialVersionUID
進行對比,如果這兩個id不一致,反序列則失敗。
5. 如果序列化與反序列化的時候可能會修改類的成員,那么最好一開始就給這個類指定一個serialVersionUID,如果一類已經指定的serialVersionUID,然后
在序列化與反序列化的時候,jvm都不會再自己算這個 class的serialVersionUID了。
6. 如果一個對象某個數據不想被序列化到硬盤上,可以使用關鍵字transient修飾。
7. 如果一個類維護了另外一個類的引用,則另外一個類也需要實現Serializable接口。

 


免責聲明!

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



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