日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java ---- 序列化

發布時間:2025/6/15 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java ---- 序列化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java對象的序列化

  • Java平臺允許我們在內存中創建可復用的Java對象,但一般情況下,只有當JVM處于運行時,這些對象才可能存在,即,這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象,并在將來重新讀取被保存的對象。Java對象序列化就能夠幫助我們實現該功能。

  • 使用Java對象序列化,在保存對象時,會把其狀態保存為一組字節,在未來,再將這些字節組裝成對象。必須注意地是,對象序列化保存的是對象的”狀態”,即它的成員變量。由此可知,對象序列化不會關注類中的靜態變量

  • 簡而言之,就是讓對象想基本數據類型和字符串類型一樣,通過輸入輸出字節流ObjectInputStream 和 ObjectOutputStream進行寫和讀操作。

Java序列化的應用場景

  • 當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候

  • 當你想用套接字在網絡上傳送對象的時候

  • 當你想通過RMI傳輸對象的時候

如何對Java對象進行序列化與反序列化

在Java中,只要一個類實現了java.io.Serializable接口,那么它就可以被序列化。

import java.io.*; import java.util.*;class User implements Serializable {private String name;private int age;private Date birthday;private transient String gender;private static int test =1;private static final long serialVersionUID = -6849794470754667710L;public User() {System.out.println("none-arg constructor");}public User(String name, Integer age,Date birthday,String gender) {System.out.println("arg constructor");this.name = name;this.age = age;this.birthday = birthday;this.gender = gender;}public void setTest(int Newtest) {this.test = Newtest;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +", birthday=" + birthday +", testStatic="+test+'}'+" "+super.toString();} }public class SerializableDemo {public static void main(String[] args) throws Exception {//Initializes The ObjectUser user = new User("qiuyu",23,new Date(),"male");System.out.println(user);user.setTest(10);System.out.println(user);//Write Obj to FileObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile"));out.writeObject(user);out.close();//Read Obj from FileObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));User newUser = (User) in.readObject();System.out.println(newUser);in.close();} }

此時的輸出為:

arg constructor User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@326de728 User{name='qiuyu', age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@4883b407
  • 此時必須注意的是,當重新讀取被保存的User對象時,并沒有調用User的任何構造器,看起來就像是直接使用字節將User對象還原出來的。

  • 當User對象被保存到tempfile文件中之后,我們可以在其它地方去讀取該文件以還原對象,但必須確保該讀取程序的CLASSPATH中包含有User.class,否則會拋出ClassNotFoundException。

Q:之前不是說序列化不保存靜態變量么,為什么這里的靜態變量進行了傳遞,都變成了10呢?
A:因為此時User.class已經被加載進了內存中,且將static變量test從1更改為了10。當我們恢復對象時,會直接獲取當前static的變量test的值,所以為10。

Q:但如果我們的恢復操作在另一個文件中進行,結果會怎么樣呢?

import java.io.FileInputStream; import java.io.ObjectInputStream;public class Recovering {public static void main(String[] args) throws Exception{//Read Obj from FileObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));User newUser = (User) in.readObject();System.out.println(newUser);in.close();} }

輸出結果為:

User{name='qiuyu', age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=1} User@6442b0a6

A:因為在運行此代碼時,User.class會被加載進內存,然后執行初始化,將被初始化為1,因此test變量會被恢復為1。注意區分上面兩種情況,不要因為是在本地進行測試,就認為static會被序列化,同時要了解Java運行時,內存加載的機制。

基本知識點

Serializable接口

  • 對于任何需要被序列化的對象,都必須要實現接口Serializable,它只是一個標識接口本身沒有任何成員,只是用來標識說明當前的實現類的對象可以被序列化.

  • 如果父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable接口。

  • 如果被寫對象的類型是String數組EnumSerializable,那么就可以對該對象進行序列化,否則將拋出NotSerializableException。

對象的讀寫

  • Java類中對象的序列化工作是通過 ObjectOutputStream ObjectInputStream 來完成的。

  • 使用readObject()writeObject()方法對對象進行讀寫操作;

  • 對于基本類型,可以使用readInt()writeInt() , readDouble()writeDouble()等類似的接口進行讀寫。

序列化機制

  • 如果僅僅只是讓某個類實現Serializable接口,而沒有其它任何處理的話,則就是使用默認序列化機制。使用默認機制,在序列化對象時,不僅會序列化當前對象本身,還會對該對象引用的其它對象也進行序列化,同樣地,這些其它對象引用的另外對象也將被序列化。

  • 在現實應用中,有些時候不能使用默認序列化機制。比如,希望在序列化過程中忽略掉敏感數據,或者簡化序列化過程。為此需要為某個字段聲明為transient,那么序列化機制就會忽略被transient修飾的字段。transient的引用變量會以null返回,基本數據類型會以相應的默認值返回。(例如:引用類型沒有實現Serializable,或者動態數據只可以在執行時求出而不能或不必存儲)

writeObject()與readObject()

  • 對于上被聲明為transient的字段gender,除了將transient關鍵字去掉之外,是否還有其它方法能使它再次可被序列化?方法之一就是在User類中添加兩個方法:writeObject()與readObject(),如下所示:

private void writeObject(ObjectOutputStream out) throws IOException {out.defaultWriteObject();out.writeUTF(gender);}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();gender = in.readUTF();}
  • 在writeObject()方法中會先調用ObjectOutputStream中的defaultWriteObject()方法,該方法會執行默認的序列化機制,此時會忽略掉gender字段。

  • 然后再調用writeUtf()方法顯示地將gender字段寫入到ObjectOutputStream中。readObject()的作用則是針對對象的讀取,其原理與writeObject()方法相同。

Q: writeObject()與readObject()都是private方法,那么它們是如何被調用的呢?
A: 毫無疑問,是使用反射。(注意這不是繼承接口的方法,因為接口類的方法都是public的,而這里的方法是private的)

Externalizable接口

  • 無論是使用transient關鍵字,還是使用writeObject()和readObject()方法,其實都是基于Serializable接口的序列化。JDK中提供了另一個序列化接口Externalizable,使用該接口之后,之前基于Serializable接口的序列化機制就將失效。

import java.io.*; import java.util.*;class UserExtern implements Externalizable {private String name;private int age;private Date birthday;private transient String gender;private static int test =1;private static final long serialVersionUID = -6849794470754667710L;public UserExtern() {System.out.println("none-arg constructor");}public UserExtern(String name, Integer age,Date birthday,String gender) {System.out.println("arg constructor");this.name = name;this.age = age;this.birthday = birthday;this.gender = gender;}private void writeObject(ObjectOutputStream out) throws IOException {out.defaultWriteObject();out.writeUTF(gender);}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();gender = in.readUTF();}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeUTF(name);out.writeInt(age);out.writeObject(birthday);out.writeUTF(gender);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {name = in.readUTF();age = in.readInt();birthday = (Date) in.readObject();gender = in.readUTF();}public void setTest(int Newtest) {this.test = Newtest;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +", birthday=" + birthday +", testStatic="+test+'}'+" "+super.toString();} }public class ExternalizableDemo {public static void main(String[] args) throws Exception {UserExtern userExtern = new UserExtern("qiuyu",23,new Date(),"male");System.out.println(userExtern);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("external"));out.writeObject(userExtern);out.close();ObjectInputStream in = new ObjectInputStream(new FileInputStream("external"));UserExtern userExtern1 = (UserExtern)in.readObject();System.out.println(userExtern1)} }

輸出結果為:

arg constructor User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@25618e91 none-arg constructor User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@604ed9f0
  • Externalizable繼承于Serializable,當使用該接口時,序列化的細節需要由程序員去完成writeExternal()readExternal()方法的具體細節,以及哪些狀態需要進行序列化。

  • 另外,若使用Externalizable進行序列化,當讀取對象時,會調用被序列化類的無參構造器去創建一個新的對象,然后再將被保存對象的字段的值分別填充到新對象中。這就是為什么在此序列化過程中UserExtern的無參構造器會被調用。由于這個原因,實現Externalizable接口的類必須要提供一個無參的構造器,且它的訪問權限為public

注意事項

  • 讀取對象的順序必須與寫入的順序相同

  • 如果有不能被序列化的對象,執行期間就會拋出NotSerializableException異常

  • 序列化時,只對對象的狀態進行保存,而不管對象的方法

  • 靜態變量不會被序列化,因為所有的對象共享同一份靜態變量的值

  • 如果一個對象的成員變量是一個對象,那么這個對象的數據成員也會被保存還原,而且會是遞歸的方式(對象網)。(序列化程序會將對象版圖上的所有東西儲存下來,這樣才能讓該對象恢復到原來的狀態)

  • 如果子類實現Serializable接口而父類未實現時,父類不會被序列化,但此時父類必須有個無參構造方法,否則會拋InvalidClassException異常;因為反序列化時會恢復原有子對象的狀態,而父類的成員變量也是原有子對象的一部分。由于父類沒有實現序列化接口,即使沒有顯示調用,也會默認執行父類的無參構造函數使變量初始化;

深入理解

序列化ID的問題

  • serialVersionUID適用于JAVA的序列化機制。簡單來說,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。

  • 在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException。

序列化存儲規則

  • Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,并不會再將對象的內容進行存儲,而只是再次存儲一份引用;

  • 序列化到同一個文件時,如第二次修改了相同對象屬性值再次保存時候,虛擬機根據引用關系知道已經有一個相同對象已經寫入文件,因此只保存第二次寫的引用,所以讀取時,都是第一次保存的對象,第二次進行的修改將無效。

public static void main(String[] args) throws Exception {//Initializes The ObjectUser user = new User("qiuyu",23,new Date(),"male");user.setTest(10);System.out.println(user);//Write Obj to FileObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile"));out.writeObject(user);user.setAge(25);System.out.println(user);out.writeObject(user);out.close();//Read Obj from FileObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile"));User newUser = (User) in.readObject();User newUser1 = (User) in.readObject();System.out.println(newUser);System.out.println(newUser1);in.close();}

輸出結果: 注意觀察age的值

User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728 User{name='qiuyu', age=25, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728 User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407 User{name='qiuyu', age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407

多次序列化的問題

  • 在一次的序列化的過程中,ObjectOutputStream 會在文件開始的地方寫入一個 Header的信息到文件中。于是在多次序列化的過程中就會繼續在文件末尾(本次序列化的開頭)寫入 Header 的信息,這時如果進行反序列化的對象的時候會報錯:java.io.StreamCorruptedException: invalid type code: AC

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的Java ---- 序列化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。