亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時間:2018年12月12日 10:38:12   作者:灰灰是菇?jīng)鲅? 
今天小編就為大家分享一篇關于Java序列化與反序列化的實例分析講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧

序列化與反序列化

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

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

Serializable接口

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

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

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

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類的實例進行序列化與反序列化操作。

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接口。

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

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

敏感字段加密

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

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改為如下所示:

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)會調(diào)用無參構造方法。

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

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

@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ī)則

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

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ī)則,當寫入文件為同一對象時,并不會再將對象的內(nèi)容進行存儲,而只是再次存儲一份引用。

總結

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

相關文章

  • JAVA像SQL一樣對List對象集合進行排序

    JAVA像SQL一樣對List對象集合進行排序

    這篇文章主要介紹了JAVA像SQL一樣對List對象集合進行排序的實現(xiàn)方法,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • 詳解在Java中如何創(chuàng)建多線程程序

    詳解在Java中如何創(chuàng)建多線程程序

    今天給大家?guī)淼氖顷P于Java的相關知識,文章圍繞著在Java中如何創(chuàng)建多線程程序展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • SpringMVC九大組件之HandlerMapping詳解

    SpringMVC九大組件之HandlerMapping詳解

    這篇文章主要介紹了SpringMVC九大組件之HandlerMapping詳解,HandlerMapping 叫做處理器映射器,它的作用就是根據(jù)當前 request 找到對應的 Handler 和 Interceptor,然后封裝成一個 HandlerExecutionChain 對象返回,需要的朋友可以參考下
    2023-09-09
  • Java 設計模式之責任鏈模式及異步責任鏈詳解

    Java 設計模式之責任鏈模式及異步責任鏈詳解

    顧名思義,責任鏈模式(Chain of Responsibility Pattern)為請求創(chuàng)建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發(fā)送者和接收者進行解耦。這種類型的設計模式屬于行為型模式
    2021-11-11
  • Spring Security實現(xiàn)登錄認證實戰(zhàn)教程

    Spring Security實現(xiàn)登錄認證實戰(zhàn)教程

    這篇文章主要介紹了Spring Security實現(xiàn)登錄認證實戰(zhàn)教程,本文通過示例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧
    2024-06-06
  • Java實現(xiàn)ATM取款機程序

    Java實現(xiàn)ATM取款機程序

    這篇文章主要為大家詳細介紹了Java實現(xiàn)ATM取款機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • java中的數(shù)學計算函數(shù)的總結

    java中的數(shù)學計算函數(shù)的總結

    這篇文章主要介紹了java中的數(shù)學計算函數(shù)的總結的相關資料,需要的朋友可以參考下
    2017-07-07
  • 文件路徑正確,報java.io.FileNotFoundException異常的原因及解決辦法

    文件路徑正確,報java.io.FileNotFoundException異常的原因及解決辦法

    這篇文章主要介紹了文件路徑正確,報java.io.FileNotFoundException異常的原因及解決辦法的相關資料,需要的朋友可以參考下
    2016-04-04
  • Java數(shù)據(jù)結構徹底理解關于KMP算法

    Java數(shù)據(jù)結構徹底理解關于KMP算法

    這篇文章主要介紹了Java數(shù)據(jù)結構關于KMP算法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • Java?延時隊列及簡單使用方式詳解

    Java?延時隊列及簡單使用方式詳解

    這篇文章主要介紹了Java延時隊列簡單使用方式,通過本文學習知道延時隊列是什么可以用來干什么,本文通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-08-08

最新評論