<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java序列化與反序列化,有哪些坑需要注意!

          共 18285字,需瀏覽 37分鐘

           ·

          2021-07-28 08:30

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          1.序列化與反序列化的概念

          先說說序列化和反序列化的概念

          序列化:將對象寫入到IO流中
          反序列化:從
          IO流中恢復(fù)對象

          Serializable接口是一個(gè)標(biāo)記接口,不用實(shí)現(xiàn)任何方法,標(biāo)記當(dāng)前類對象是可以序列化的,是給JVM看的。

          ??序列化機(jī)制允許將這些實(shí)現(xiàn)序列化接口的對象轉(zhuǎn)化為字節(jié)序列,這些字節(jié)序列可以保證在磁盤上或者網(wǎng)絡(luò)傳輸后恢復(fù)成原來的對象。序列化就是把對象存儲(chǔ)在JVM以外的地方,序列化機(jī)制可以讓對象脫離程序的運(yùn)行而獨(dú)立存在。

          序列化在業(yè)務(wù)代碼也許用的不多,但是在框架層面用的是很多的。

          相關(guān)技術(shù):Session的序列化或者反序列化

          先給出序列化的例子,請記住這個(gè)People類,后面會(huì)根據(jù)這個(gè)類來改造講解。

          public class People {

              private Long id;

              public People(Long id) {
                  this.id = id;
              }

              public Long getId() {
                  return id;
              }

              public void setId(Long id) {
                  this.id = id;
              }

              @Override
              public String toString() {
                  return "People{" +
                          "id=" + id +
                          '}';
              }
          }


          import java.io.*;

          // 屏蔽編譯器的警告
          @SuppressWarnings("all")
          public class Main {

              /**
               * <h1>序列化和反序列化 People 對象</h1>
               */
              private static void testSerializablePeople() throws Exception {

                  // 序列化的步驟

                  // 用于存儲(chǔ)序列化的文件,這里的java_下劃線僅僅為了說明是java序列化對象,沒有任何其他含義
                  File file = new File("/tmp/people_10.java_");
                  if (!file.exists()) {
                      // 1,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
                      file.getParentFile().mkdirs();
                      try {
                          // 2,再創(chuàng)建文件
                          file.createNewFile();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  People p = new People(10L);

                  // 創(chuàng)建一個(gè)輸出流
                  ObjectOutputStream oos = new ObjectOutputStream(
                          new FileOutputStream(file)
                  );
                  // 輸出可序列化對象
                  oos.writeObject(p);
                  // 關(guān)閉輸出流
                  oos.close();

                  // 反序列化的步驟

                  // 創(chuàng)建一個(gè)輸入流
                  ObjectInputStream ois = new ObjectInputStream(
                          new FileInputStream(file)
                  );
                  // 得到反序列化的對象,這里可以強(qiáng)轉(zhuǎn)為People類型
                  Object newPerson = ois.readObject();
                  // 關(guān)閉輸入流
                  ois.close();

                  System.out.println(newPerson);
              }

              public static void main(String[] args) throws Exception {
                  testSerializablePeople();
              }
          }

          運(yùn)行之后,看到磁盤文件因?yàn)樾蛄谢嗔艘粋€(gè)文件

          控制臺(tái)中因反序列化輸出的對象信息打印如下:

          2.子類實(shí)現(xiàn)Serializable接口,父類沒有實(shí)現(xiàn),子類可以序列化嗎?

          去掉父類Peopleimplements Serializable,讓父類不實(shí)現(xiàn)序列化接口,子類Worker實(shí)現(xiàn)序列化接口

          public class Worker extends People implements Serializable {

              private String name;
              private Integer age;

              public Worker(Long id, String name, Integer age) {
                  super(id);
                  this.name = name;
                  this.age = age;
              }

          }


           public static void main(String[] args) throws Exception {
                  testSerizableWorker();
              }

              /**
               * <h2>子類實(shí)現(xiàn)序列化, 父類不實(shí)現(xiàn)序列化</h2>
               * */
              private static void testSerizableWorker() throws Exception {

                  File file = new File("/tmp/worker_10.java_");
                  if (!file.exists()) {
                      // 1,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
                      file.getParentFile().mkdirs();
                      try {
                          // 2,再創(chuàng)建文件
                          file.createNewFile();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  Worker p = new Worker(10L, "lcy", 18);

                  // 創(chuàng)建一個(gè)輸出流
                  ObjectOutputStream oos = new ObjectOutputStream(
                          new FileOutputStream(file)
                  );
                  // 輸出可序列化對象
                  oos.writeObject(p);
                  // 關(guān)閉輸出流
                  oos.close();

                  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                  Object newWorker = ois.readObject(); // 父類沒有序列化的時(shí)候,需要調(diào)用父類的無參數(shù)構(gòu)造方法
                  ois.close();
                  System.out.println(newWorker);
              }

          再次測試運(yùn)行

          ??結(jié)果顯示沒有有效地構(gòu)造器,原來是因?yàn)楦割悰]有序列化的時(shí)候,Object newWorker = ois.readObject()需要直接調(diào)用父類的無參數(shù)構(gòu)造方法,不經(jīng)過子類的無參構(gòu)造方法。

          我們在父類People中加上空的構(gòu)造方法之后再次執(zhí)行

          ??結(jié)果卻發(fā)現(xiàn)打印的不是Worker,而是父類People,因?yàn)樽宇悰]有實(shí)現(xiàn)toString而調(diào)用父類的toString,所以打印了People對象,至于父類成員變量id為什么是null,原因如下:

          ??一個(gè)子類實(shí)現(xiàn)了 Serializable 接口,它的父類都沒有實(shí)現(xiàn) Serializable接口,序列化該子類對象。要想反序列化后輸出父類定義的某變量的數(shù)值,就需要讓父類也實(shí)現(xiàn)Serializable接口或者父類有默認(rèn)的無參的構(gòu)造函數(shù)。 

          ??在父類沒有實(shí)現(xiàn)
          Serializable 接口時(shí),虛擬機(jī)是不會(huì)序列化父對象的,而一個(gè) Java對象的構(gòu)造必須先有父對象,才有子對象,反序列化也不例外。所以反序列化時(shí),為了構(gòu)造父對象,只能調(diào)用父類的無參構(gòu)造函數(shù)作為默認(rèn)的父對象。因此當(dāng)我們?nèi)「笇ο蟮淖兞恐禃r(shí),它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值,如果在父類無參構(gòu)造函數(shù)中沒有對變量賦值,那么父類成員變量值都是默認(rèn)值,如這里的Long型就是null

          ??根據(jù)以上特性,我們可以將不需要被序列化的字段抽取出來放到父類中,子類實(shí)現(xiàn) 
          Serializable接口,父類不實(shí)現(xiàn)Serializable接口但提供一個(gè)空構(gòu)造方法,則父類的字段數(shù)據(jù)將不被序列化。

          最后加上子類WorkertoString方法,打印結(jié)果如下:

          總結(jié):
          子類實(shí)現(xiàn)Serializable接口,父類沒有實(shí)現(xiàn),子類可以序列化!!
          這種情況父類一定要提供空構(gòu)造方法,不要忘了子類的toString方法!

          3.類中存在引用對象,這個(gè)類對象在什么情況下可以實(shí)現(xiàn)序列化?

          ??來一個(gè)組合對象,里面引用People對象,此時(shí)People對象沒有實(shí)現(xiàn)Serializable接口,能否序列化呢?代碼給出來,大家可以自行復(fù)制測試一下。

          public class Combo implements Serializable {

              private int id;
              private People people;

              public Combo(int id, People people) {
                  this.id = id;
                  this.people = people;
              }

              public int getId() {
                  return id;
              }

              public void setId(int id) {
                  this.id = id;
              }

              public People getPeople() {
                  return people;
              }

              public void setPeople(People people) {
                  this.people = people;
              }
              
              @Override
              public String toString() {
                  return "Combo{" +
                          "id=" + id +
                          ", people=" + people +
                          '}';
              }
          }

          public class People {

              private Long id;

              public People() {
              }

              public People(Long id) {
                  this.id = id;
              }

              public Long getId() {
                  return id;
              }

              public void setId(Long id) {
                  this.id = id;
              }

              @Override
              public String toString() {
                  return "People{" +
                          "id=" + id +
                          '}';
              }
          }

              private static void testSerializableCombo() throws Exception {

                  File file = new File("/tmp/combo_10.java_");
                  if (!file.exists()) {
                      // 1,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
                      file.getParentFile().mkdirs();
                      try {
                          // 2,再創(chuàng)建文件
                          file.createNewFile();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  Combo p = new Combo(1, new People(10L));

                  // 創(chuàng)建一個(gè)輸出流
                  ObjectOutputStream oos = new ObjectOutputStream(
                          new FileOutputStream(file)
                  );
                  // 輸出可序列化對象
                  oos.writeObject(p);
                  // 關(guān)閉輸出流
                  oos.close();

                  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                  Object newCombo = ois.readObject();
                  ois.close();
                  System.out.println(newCombo);
              }

              public static void main(String[] args) throws Exception {
                  testSerializableCombo();
              }

          運(yùn)行結(jié)果如下

          直接爆出異常,說明People類沒有序列化。
          當(dāng)
          People加上implements Serializable實(shí)現(xiàn)序列化接口后,再次執(zhí)行如下

          總結(jié):
          ??一個(gè)類里面所有的屬性必須是可序列化的,這個(gè)類才能順利的序列化。比如,類中存在引用對象,那么這個(gè)引用對象必須是可序列化的,這個(gè)類才能序列化。

          4.同一個(gè)對象多次序列化之間有屬性更新,前后的序列化有什么區(qū)別?

          ??下面例子中People是可序列化的,每次序列化之前都會(huì)把Peopleid值修改了,用來觀察看看,多次序列化期間,如果對象屬性更新,是否會(huì)影響序列化,反序列化有什么區(qū)別。

              /**
               * <h2>同一個(gè)對象多次序列化的問題, 坑</h2>
               * */
              private static void sameObjectRepeatedSerialization() throws Exception {

                  File file = new File("/tmp/peopele_more.java_");
                  if (!file.exists()) {
                      // 1,先得到文件的上級(jí)目錄,并創(chuàng)建上級(jí)目錄
                      file.getParentFile().mkdirs();
                      try {
                          // 2,再創(chuàng)建文件
                          file.createNewFile();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  People p = new People(10L);
                  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
                  // 未序列化,先修改屬性
                  p.setId(11L);
                  oos.writeObject(p);
                  // 序列化一次后,再次修改屬性
                  p.setId(15L);
                  oos.writeObject(p);
                  // 序列化兩次后,再次修改屬性
                  p.setId(20L);
                  oos.writeObject(p);
                  oos.close();

                  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                  Object people1 = ois.readObject();
                  Object people2 = ois.readObject();
                  Object people3 = ois.readObject();
                  ois.close();

                  System.out.println(((People) people1).getId());
                  System.out.println(((People) people2).getId());
                  System.out.println(((People) people3).getId());
              }


              public static void main(String[] args) throws Exception {
                  sameObjectRepeatedSerialization();
              }

          運(yùn)行結(jié)果如下

          ??結(jié)果發(fā)現(xiàn)反序列化讀出的值都是一樣的。說明當(dāng)對象第一次序列化成功后,后續(xù)這個(gè)對象屬性即使有修改,也不會(huì)對后面的序列化造成成影響。

          ??這其實(shí)是序列化算法的原因,所有要序列化的對象都有一個(gè)序列化的編碼號(hào),當(dāng)試圖序列化一個(gè)對象,會(huì)檢查這個(gè)對象是否已經(jīng)序列化過,若從未序列化過,才會(huì)序列化為字節(jié)序列去輸出。若已經(jīng)序列化過,則會(huì)輸出一個(gè)編碼符號(hào),不會(huì)重復(fù)序列化一個(gè)對象。如下

          序列化一次后,后續(xù)繼續(xù)序列化并未重復(fù)轉(zhuǎn)換為字節(jié)序列,而是輸出字符q~

          總結(jié):
          ??當(dāng)?shù)谝淮涡蛄谢螅还苋绾涡薷倪@個(gè)對象的屬性,都不會(huì)對后續(xù)的序列化產(chǎn)生影響,反序列化的結(jié)果都和第一次相同。



            作者 |  磚業(yè)洋__

          來源 |  https://blog.csdn.net/qq_34115899/article/details/118463573


          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天堂国产在线 | 欧美成人超碰在线 | 能看熟女不卡视频 | 91成人大片 | 伊人电影AV |