天天看點

對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)

對象的輸入輸出流 : 主要的作用是用于寫入對象資訊與讀取對象資訊。 對象資訊一旦寫到檔案上那麼對象的資訊就可以做到持久化了   對象的輸出流: ObjectOutputStream   對象的輸入流:  ObjectInputStream

使用:

對象的輸出流将指定的對象寫入到檔案的過程,就是将對象序列化的過程,對象的輸入流将指定序列化好的檔案讀出來的過程,就是對象反序列化的過程。既然對象的輸出流将對象寫入到檔案中稱之為對象的序列化,那麼可想而知對象所對應的class必須要實作Serializable接口。(檢視源碼可得知:Serializable接口沒有任何的方法,隻是作為一個辨別接口存在)。

1、将User類的對象序列化

運作程式得到記事本中存入的資訊:可見已經序列化到記事本中

2、将序列化到記事本的内容反序列化

對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)
對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)

運作代碼得到的結果:

賬号:酒香逢 密碼:123 OK

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

對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)
對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)

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

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 

對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)
對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)

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

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

對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)
對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)

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

對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)
對象輸入輸出流ObjectInputStream、ObjectOutputStream(對象序列化與反序列化)

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

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接口。