對象的輸入輸出流 : 主要的做用是用於寫入對象信息與讀取對象信息。 對象信息一旦寫到文件上那麼對象的信息就能夠作到持久化了
對象的輸出流: ObjectOutputStream
對象的輸入流: ObjectInputStreamjava
使用:jvm
對象的輸出流將指定的對象寫入到文件的過程,就是將對象序列化的過程,對象的輸入流將指定序列化好的文件讀出來的過程,就是對象反序列化的過程。既然對象的輸出流將對象寫入到文件中稱之爲對象的序列化,那麼可想而知對象所對應的class必需要實現Serializable接口。(查看源碼可得知:Serializable接口沒有任何的方法,只是做爲一個標識接口存在)。ide
一、將User類的對象序列化ui
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 }
運行程序獲得記事本中存入的信息:可見已經序列化到記事本中this
二、將序列化到記事本的內容反序列化spa
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 }
運行代碼獲得的結果:code
帳號:酒香逢 密碼:123
OK對象
可是,若是這時候這個obj.txt是咱們項目中一個文件,而項目到後期在原來User類的基礎上添加成員變量String userName;blog
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與項目中的不一致,即找不到對應的類來反序列化。
三、若是序列化與反序列化的時候可能會修改類的成員,那麼最好一開始就給這個類指定一個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字段解註釋),再一次執行反序列化操做,執行的結果跟以前反序列化的結果是一致的。可見這樣解決後咱們後期修改類也是可行的。
四、若是在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 }
五、最後再提一下關鍵字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接口。