對象的輸入輸出流 : 主要的作用是用於寫入對象信息與讀取對象信息。 對象信息一旦寫到文件上那么對象的信息就可以做到持久化了
對象的輸出流: 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接口。