亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

Java序列化與反序列化的實例分析講解

2024-07-14 08:43:11
字體:
來源:轉載
供稿:網友

序列化與反序列化

Java對象是有生命周期的,當生命周期結束它就會被回收,但是可以通過將其轉換為字節序列永久保存下來或者通過網絡傳輸給另一方。

把對象轉換為字節序列的過程稱為對象的序列化;把字節序列恢復為對象的過程稱為對象的反序列化。

Serializable接口

一個類實現java.io.Serializable接口就可以被序列化或者反序列化。實際上,Serializable接口中沒有任何變量和方法,它只是一個標識。如果沒有實現這個接口,在序列化或者反序列化時會拋出NotSerializableException異常。

下面是一個實現了Serializable接口的類以及它的序列化與反序列化過程。

public class SerialTest {  public static void main(String[] args) {    Test test = new Test();    test.setName("test");    // 序列化,存儲對象到文本    ObjectOutputStream oos = null;    try {      oos = new ObjectOutputStream(new FileOutputStream("test"));      oos.writeObject(test);    } catch (IOException e) {      e.printStackTrace();    } finally {      try {        if (oos != null) {          oos.close();        }      } catch (IOException e) {        e.printStackTrace();      }    }    // 反序列化,從文本中取出對象    ObjectInputStream ois = null;    try {      ois = new ObjectInputStream(new FileInputStream("test"));      Test1 test1 = (Test1) ois.readObject();    } catch (IOException e) {      e.printStackTrace();    } catch (ClassNotFoundException e) {      e.printStackTrace();    } finally {      try {        if (ois != null) {          ois.close();        }      } catch (IOException e) {        e.printStackTrace();      }    }  }}class Test implements Serializable {  private String name;  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  @Override  public String toString() {    return "Test{" +        "name='" + name + '/'' +        '}';  }}

運行結果:

Test{name='test'}

serialVersionUID

private static final long serialVersionUID = -3297006450515623366L;

serialVersionUID是一個序列化版本號,實現Serializable接口的類都會有一個版本號。如果沒有自己定義,那么程序會默認生成一個版本號,這個版本號是Java運行時環境根據類的內部細節自動生成的。最好我們自己定義該版本號,否則當類發生改變時,程序為我們自動生成的序列化版本號也會發生改變,那么再將原來的字節序列反序列化時就會發生錯誤。

下面是將Test1類加入一個變量age,此時再進行反序列化的結果??梢钥闯?,序列化版本號已發生改變,程序認為不是同一個類,不能進行反序列化。

java.io.InvalidClassException: test.Test1; local class incompatible: stream classdesc serialVersionUID = 9097989105451761251, local class serialVersionUID = -7756223913249050270 at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689) at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1903) at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1772) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2060) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1594) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430) at test.SerialTest.main(SerialTest.java:11)

為了提高serialVersionUID的獨立性和確定性,強烈建議在一個可序列化類中顯示地定義serialVersionUID,為他賦予明確的值。

那么在IDEA中,怎么手動生成呢?

在settings->Editor->Inspections下,搜索serial,開啟Serializable class without 'serialVersionUID'的拼寫檢查,然后將光標放在實現Serializable的接口上,按住ALt+Enter鍵,選擇添加serialVersionUID即可。

Transient關鍵字

transient修飾類的變量,可以使變量不被序列化。反序列化時,被transient修飾的變量的值被設為初始值,如int類型被設為0,對象型被設為null。

ObjectOutputStream類和ObjectInputStream類

ObjectOutputStream的writeObject方法可以序列化對象,ObjectInputStream的readObject可以反序列化對象。ObjectOutputStream實現了接口ObjectOutput,所以可以進行對象寫操作。ObjectInputStream實現了接口ObjectInput,所以可以對對象進行讀操作。

靜態變量序列化

給Test類中增加一個靜態變量,賦值為12,然后在序列化之后修改其值為10,反序列化之后打印它的值。發現打印的值為10,之前的12并沒有被保存。

靜態變量是不參與序列化的,序列化只是用來保存對象的狀態,而靜態變量屬于類的狀態。

父類序列化

讓Test繼承一個沒有實現Serializable接口的類,設置父類中變量的值,對Test類的實例進行序列化與反序列化操作。

public class SerialTest {  public static void main(String[] args) {    Test test = new Test();    test.setName("huihui");    test.setSex(12);    // 序列化,存儲對象到文本    ObjectOutputStream oos = null;    try {      oos = new ObjectOutputStream(new FileOutputStream("test"));      oos.writeObject(test);    } catch (IOException e) {      e.printStackTrace();    } finally {      try {        if (oos != null) {          oos.close();        }      } catch (IOException e) {        e.printStackTrace();      }    }    // 反序列化,從文本中取出對象    ObjectInputStream ois = null;    try {      ois = new ObjectInputStream(new FileInputStream("test"));      Test test1 = (Test) ois.readObject();      System.out.println(test1);    } catch (IOException | ClassNotFoundException e) {      e.printStackTrace();    } finally {      try {        if (ois != null) {          ois.close();        }      } catch (IOException e) {        e.printStackTrace();      }    }  }}class Test extends TestFather implements Serializable {  private static final long serialVersionUID = 4335715933640891747L;  private String name;  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  @Override  public String toString() {    return "Test{" +        "name='" + name + '/'' +        "sex='" + sex + '/'' +        '}';  }}class TestFather {  protected Integer sex;  public Integer getSex() {    return sex;  }  public void setSex(Integer sex) {    this.sex = sex;  }  @Override  public String toString() {    return "TestFather{" +        "sex='" + sex + '/'' +        '}';  }}

運行結果:

Test{name='huihui'sex='null'}

發現雖然對sex進行了復制,但是反序列化結果仍然為null。

現在讓TestFather類實現Serializable接口,運行結果如下。所以當我們想要序列化父類的變量時,也需要讓父類實現Serializable接口。

Test{name='huihui'sex='12'}

同理,如果Test類中有任何變量是對象,那么該對象的類也需要實現Serializable接口。查看String源代碼,確實實現了Serializable接口。大家可以測試一下字段的類不實現Serializable接口的情況,運行會直接報java.io.NotSerializableException異常。

敏感字段加密

如果對于某些字段我們并不想直接暴露出去,需要對其進行加密處理,那么就需要我們自定義序列化和反序列化方法。使用Serializable接口進行序列化時,如果不自定義方法,則默認調用ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。下面我們來嘗試一下自己實現序列化與反序列化過程。

class Test implements Serializable {  private static final long serialVersionUID = 4335715933640891747L;  private String name;  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  @Override  public String toString() {    return "Test{" +        "name='" + name + '/'' +        '}';  }  private void writeObject(ObjectOutputStream out) {    try {      ObjectOutputStream.PutField putField = out.putFields();      System.out.println("原name:" + name);      // 模擬加密      name = "change";      putField.put("name", name);      System.out.println("加密后的name:" + name);      out.writeFields();    } catch (IOException e) {      e.printStackTrace();    }  }  private void readObject(ObjectInputStream in) {    try {      ObjectInputStream.GetField getField = in.readFields();      Object object = getField.get("name", "");      System.out.println("要解密的name:" + object.toString());      name = "huihui";    } catch (IOException e) {      e.printStackTrace();    } catch (ClassNotFoundException e) {      e.printStackTrace();    }  }}

運行結果:

原name:huihui
加密后的name:change
要解密的name:change
解密后的name:huihui

這種寫法重寫了writeObject方法和readObject方法,下面一種接口也可以實現相同的功能。

Externalizable接口

除了Serializable接口,Java還提供了一個Externalizable接口,它繼承了Serializable接口,提供了writeExternal和readExternal兩個方法,實現該接口的類必須重寫這兩個方法。同時還發現,類還必須提供一個無參構造方法,否則會報java.io.InvalidClassException異常。

先不深究為什么要加一個無參構造方法,我們先試一下這個接口的序列化效果。將類Test改為如下所示:

class Test implements Externalizable {  private static final long serialVersionUID = 4335715933640891747L;  private String name;  public Test() {  }  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  @Override  public String toString() {    return "Test{" +        "name='" + name + '/'' +        '}';  }  @Override  public void writeExternal(ObjectOutput out) throws IOException {  }  @Override  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  }}

再次運行測試方法,發現輸出的name是null。在readObject處打斷點,發現會調用無參構造方法。

name其實并沒有被序列化與反序列化,writeExternal方法和readExternal方法中是需要我們自己來實現序列化與反序列化的細節的。在反序列化時,會首先調用類的無參考構造方法創建一個新對象,然后再填充每個字段。

我們對writeExternal方法和readExternal方法進行重寫:

@Overridepublic void writeExternal(ObjectOutput out) throws IOException {  out.writeObject(name);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  name = (String) in.readObject();}

此時運行測試方法,發現Test類被正常序列化與反序列化。

序列化存儲規則

當多次序列化一個對象時,是會序列化多次還是會序列化一次呢?

public class SerialTest {  public static void main(String[] args) {    Test test = new Test();    test.setName("huihui");    // 序列化,存儲對象到文本    ObjectOutputStream oos = null;    try {      oos = new ObjectOutputStream(new FileOutputStream("test"));      // 兩次寫入文件      oos.writeObject(test);      oos.flush();      System.out.println(new File("test").length());      oos.writeObject(test);      oos.flush();      System.out.println(new File("test").length());    } catch (IOException e) {      e.printStackTrace();    } finally {      try {        if (oos != null) {          oos.close();        }      } catch (IOException e) {        e.printStackTrace();      }    }    // 反序列化,從文本中取出對象    ObjectInputStream ois = null;    try {      ois = new ObjectInputStream(new FileInputStream("test"));      // 讀取兩個對象      Test test1 = (Test) ois.readObject();      Test test2 = (Test) ois.readObject();      System.out.println(test1 == test1);    } catch (IOException | ClassNotFoundException e) {      e.printStackTrace();    } finally {      try {        if (ois != null) {          ois.close();        }      } catch (IOException e) {        e.printStackTrace();      }    }  }}class Test implements Serializable {  private static final long serialVersionUID = 4335715933640891747L;  private String name;  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  @Override  public String toString() {    return "Test{" +        "name='" + name + '/'' +        '}';  }}

運行結果:

73
78
true

可以發現,當第二次寫入對象時,文件的長度僅僅增加了5個字節,并且在判等時,兩個引用指向同一地址。

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對VeVb武林網的支持。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美丰满老妇厨房牲生活| 久久久成人av| 欧美日韩免费看| 日韩精品免费在线视频观看| 亚洲人成网7777777国产| 亚洲天堂av高清| 色偷偷av亚洲男人的天堂| 国产精品无av码在线观看| 中文字幕亚洲一区在线观看| 国产精品成人免费电影| 欧美成人手机在线| 亚洲精品电影在线| 高清视频欧美一级| 黑人巨大精品欧美一区免费视频| 国产情人节一区| 青青久久aⅴ北条麻妃| www国产亚洲精品久久网站| 久久久亚洲国产天美传媒修理工| 亚洲精品第一页| 欧美成人免费一级人片100| 欧美日韩一二三四五区| 国产精品丝袜久久久久久高清| 亚洲精品v欧美精品v日韩精品| 日韩在线视频免费观看高清中文| 综合久久五月天| 亚洲国产三级网| 在线丨暗呦小u女国产精品| 成人有码在线播放| 狠狠色狠狠色综合日日小说| 992tv成人免费视频| 成人欧美一区二区三区在线湿哒哒| 久久久久久久久久亚洲| 亚洲男女性事视频| 久久国产精品免费视频| 亚洲丝袜一区在线| 亚洲一区二区三区xxx视频| 亚洲天堂影视av| 久久影院免费观看| 国产精品成人一区二区三区吃奶| 国产欧美最新羞羞视频在线观看| 日韩亚洲精品电影| 欧美最猛性xxxxx(亚洲精品)| 黄色一区二区在线| 亚洲欧洲xxxx| 8x海外华人永久免费日韩内陆视频| 国产精品自产拍在线观看| 成人亲热视频网站| 久久精品最新地址| 国产精品成人一区| 18一19gay欧美视频网站| 国产精品91久久久| 中文字幕欧美日韩在线| 欧美性理论片在线观看片免费| 欧美精品在线免费| 久久精品国产成人精品| 91综合免费在线| 亚洲一区中文字幕在线观看| 搡老女人一区二区三区视频tv| 中文字幕少妇一区二区三区| 久久视频免费在线播放| 亚洲日韩中文字幕在线播放| 人妖精品videosex性欧美| 精品高清美女精品国产区| 91地址最新发布| 在线观看亚洲区| 欧美精品在线免费观看| 最新的欧美黄色| 中文字幕亚洲一区| 欧美性xxxxx极品| 色婷婷成人综合| 国产一区二区欧美日韩| 欧美在线欧美在线| 欧美在线一区二区视频| 久久久91精品国产| 成人乱人伦精品视频在线观看| 亚洲欧美日韩在线高清直播| 国产69精品99久久久久久宅男| 日韩在线视频观看正片免费网站| 97婷婷大伊香蕉精品视频| 海角国产乱辈乱精品视频| 欧美在线一区二区视频| 海角国产乱辈乱精品视频| 亚洲japanese制服美女| 亚洲一区二区免费| 亚洲国产成人一区| 国产一区二中文字幕在线看| 国产综合久久久久| 欧美疯狂做受xxxx高潮| 国产精品久久久久久久久久新婚| 国模精品视频一区二区三区| 最近2019年好看中文字幕视频| 亚洲一区美女视频在线观看免费| 亚洲午夜av电影| 成人在线免费观看视视频| 日韩电影免费观看在线观看| 一区二区三区视频在线| 亚洲国产精品久久久久久| 日韩免费中文字幕| 国产一区欧美二区三区| 中文字幕欧美日韩| 久久久影视精品| 午夜精品免费视频| 国内免费久久久久久久久久久| 日本电影亚洲天堂| 中文字幕在线观看亚洲| 国产成人在线亚洲欧美| 777777777亚洲妇女| 美日韩精品免费观看视频| 亚洲欧美日韩图片| 2021久久精品国产99国产精品| 粗暴蹂躏中文一区二区三区| 欧美激情一区二区三区成人| 啪一啪鲁一鲁2019在线视频| 亚洲午夜未删减在线观看| 亚洲电影中文字幕| 国产日韩av高清| 日韩在线观看免费全集电视剧网站| 最近2019年手机中文字幕| 亚洲男人天堂网站| 国产日韩在线免费| 日韩最新av在线| 久久全国免费视频| 亚洲电影在线观看| 欧美有码在线观看| 国产精品91久久| 国产丝袜一区二区三区| 少妇高潮久久久久久潘金莲| 成人av.网址在线网站| 国产精品一二区| 亚洲小视频在线观看| 久久久久久成人| 美女少妇精品视频| 亚洲综合自拍一区| 亚洲精品wwww| 国产精品福利无圣光在线一区| 亚洲免费小视频| 日韩在线观看精品| 亚洲色图色老头| 尤物九九久久国产精品的分类| 国产精选久久久久久| 亚洲综合在线中文字幕| 欧美国产第一页| 亚洲在线一区二区| 欧美视频在线免费| 色先锋久久影院av| 欧美日韩福利视频| 亚洲欧美999| 欧美中文字幕精品| 欧美午夜无遮挡| 亚洲综合av影视| 自拍偷拍免费精品| 久久久久日韩精品久久久男男| 亚洲欧美第一页| 亚洲男人天堂网站| 亚洲美女精品成人在线视频| 8090理伦午夜在线电影| 亚洲欧洲中文天堂| 国产精品流白浆视频| 国产精品亚洲аv天堂网| 一个人看的www欧美| 91久久精品国产| 亚洲精品v欧美精品v日韩精品| 亚洲国产精品一区二区三区| 欧美成人午夜影院|