一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java序列化與反序列化的實例分析講解

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

2021-06-21 13:45灰灰是菇涼呀 Java教程

今天小編就為大家分享一篇關于Java序列化與反序列化的實例分析講解,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧

序列化反序列化

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

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

serializable接口

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

下面是一個實現(xiàn)了serializable接口的類以及它的序列化與反序列化過程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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

?
1
private static final long serialversionuid = -3297006450515623366l;

serialversionuid是一個序列化版本號,實現(xiàn)serializable接口的類都會有一個版本號。如果沒有自己定義,那么程序會默認生成一個版本號,這個版本號是java運行時環(huán)境根據(jù)類的內部細節(jié)自動生成的。最好我們自己定義該版本號,否則當類發(fā)生改變時,程序為我們自動生成的序列化版本號也會發(fā)生改變,那么再將原來的字節(jié)序列反序列化時就會發(fā)生錯誤。

下面是將test1類加入一個變量age,此時再進行反序列化的結果。可以看出,序列化版本號已發(fā)生改變,程序認為不是同一個類,不能進行反序列化。

?
1
2
3
4
5
6
7
8
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'的拼寫檢查,然后將光標放在實現(xiàn)serializable的接口上,按住alt+enter鍵,選擇添加serialversionuid即可。

transient關鍵字

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

objectoutputstream類和objectinputstream類

objectoutputstream的writeobject方法可以序列化對象,objectinputstream的readobject可以反序列化對象。objectoutputstream實現(xiàn)了接口objectoutput,所以可以進行對象寫操作。objectinputstream實現(xiàn)了接口objectinput,所以可以對對象進行讀操作。

靜態(tài)變量序列化

給test類中增加一個靜態(tài)變量,賦值為12,然后在序列化之后修改其值為10,反序列化之后打印它的值。發(fā)現(xiàn)打印的值為10,之前的12并沒有被保存。

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

父類序列化

讓test繼承一個沒有實現(xiàn)serializable接口的類,設置父類中變量的值,對test類的實例進行序列化與反序列化操作。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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'}

發(fā)現(xiàn)雖然對sex進行了復制,但是反序列化結果仍然為null。

現(xiàn)在讓testfather類實現(xiàn)serializable接口,運行結果如下。所以當我們想要序列化父類的變量時,也需要讓父類實現(xiàn)serializable接口。

?
1
test{name='huihui'sex='12'}

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

敏感字段加密

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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方法,下面一種接口也可以實現(xiàn)相同的功能。

externalizable接口

除了serializable接口,java還提供了一個externalizable接口,它繼承了serializable接口,提供了writeexternal和readexternal兩個方法,實現(xiàn)該接口的類必須重寫這兩個方法。同時還發(fā)現(xiàn),類還必須提供一個無參構造方法,否則會報java.io.invalidclassexception異常。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 {
  }
}

再次運行測試方法,發(fā)現(xiàn)輸出的name是null。在readobject處打斷點,發(fā)現(xiàn)會調用無參構造方法。

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

我們對writeexternal方法和readexternal方法進行重寫:

?
1
2
3
4
5
6
7
8
@override
public void writeexternal(objectoutput out) throws ioexception {
  out.writeobject(name);
}
@override
public void readexternal(objectinput in) throws ioexception, classnotfoundexception {
  name = (string) in.readobject();
}

此時運行測試方法,發(fā)現(xiàn)test類被正常序列化與反序列化。

序列化存儲規(guī)則

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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

可以發(fā)現(xiàn),當?shù)诙螌懭雽ο髸r,文件的長度僅僅增加了5個字節(jié),并且在判等時,兩個引用指向同一地址。

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對服務器之家的支持。如果你想了解更多相關內容請查看下面相關鏈接

原文鏈接:https://blog.csdn.net/sinat_28394909/article/details/84956158

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 99欧美视频 | 动漫美女被褥吸奶漫画漫画 | 波多野结中文字幕在线69视频 | 色综合91久久精品中文字幕 | 羞羞视频免费观看网站 | 护士伦理片| 国产男人天堂 | xxxxxx国产精品视频 | 日产精品一二三四区国产 | 狠狠色狠狠色综合婷婷tag | 欧美视频一区二区三区在线观看 | 日韩欧美亚洲一区二区综合 | 99久久精品免费看国产高清 | 国产欧美久久一区二区 | 欧美一级在线播放 | 亚洲男人天堂影院 | 免费视频专区一国产盗摄 | 亚洲嫩模吧粉嫩粉嫩冒白浆 | 日韩免费在线视频 | 成人在线免费看 | 91精品国产高清久久久久久91 | 国内精品免费一区二区三区 | 欧美日韩一级视频 | 四虎在线成人免费网站 | 五月天网站 | 俺去啦最新官网 | 美女脱得一二净无内裤全身的照片 | 给我免费的视频在线观看 | 日本四虎影视 | 啊好爽视频 | 日韩欧美中文字幕一区 | 国产精品毛片va一区二区三区 | 日本一道高清不卡免费 | 黄 在线播放 | 无码日韩精品一区二区免费 | 俄罗斯烧性春三级k8播放 | 亚洲国产综合网 | 91香蕉国产视频 | 欧美在线视频免费播放 | 亚洲精品一区二区三区在线播放 | 青青精品 |