十五、序列化

1、概念

(1)序列化
將數據結構或者對象轉換成二進制序列的過程
(2)反序列化
將序列化過程所生成的二進制序列轉換成數據結構或者對象的過程
(3)持久化
將數據結構或者對象存儲起來,如內存、磁盤。

2、序列化的作用

進程之間、客戶端和服務器之間數據的傳輸。因爲傳輸過程只能傳輸二進制序列。
例如如下圖:



在客戶端和服務器之間網絡數據傳輸的時候,我們可以選擇Serializable序列化或者廣義的json,xml,protbuf ,在安卓的進程之間通信的時候,可以選擇Parcelable 序列化。

3、序列化的類型

(1)Serializable
Java獨有的序列化
(2) Parcelable
Android獨有的序列
(3)廣義的序列化
json,xml,protbuf .

4、Serializable序列化方式

對象通過實現Serializable接口或者Externalizable接口,Externalizable接口也是實現了Serializable接口

關於Serializable的幾個問題

(1)serialVersionUID

serialVersionUID通常是對象的哈希碼,主要用於對象的版本控制唯一標識。
序列化和反序列化的時候類中的serialVersionUID一定要一致。如果序列化時的serialVersionUID和反序列化時的serialVersionUID不一樣,將會拋出異常。舉個例子
我們定義了Student 類中的serialVersionUID = 1,然後將其序列化到磁盤在反序列化出來的時候,不修改serialVersionUID

package com.it.test.seriv;


import java.io.Serializable;

public class Student implements Serializable {
    private static final long serialVersionUID = 1;
    private String name;
    private String sex;
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
    public Student(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
    public static void main(String[] args) {
        //序列化
        Student student =  new Student("小明","男");
        System.out.println("寫入....");
        SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");

        //反序列化
        Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
        System.out.println("讀出....");
        System.out.println(student1.toString());
    }
}

序列化和反序列化都成功

寫入....
讀出....
Student{name='小明', sex='男'}

我們將serialVersionUID 改成2,再反序列化出來 試試

    private static final long serialVersionUID = 2;
    public static void main(String[] args) {
        //序列化
//        Student student =  new Student("小明","男");
//        System.out.println("寫入....");
//        SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");

        //反序列化
        Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
        System.out.println("讀出....");
        System.out.println(student1.toString());
    }

提示serialVersionUID 不一致的異常。

java.io.InvalidClassException: com.it.test.seriv.Student; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at com.it.test.seriv.SerializeableUtils.readObject(SerializeableUtils.java:71)
    at com.it.test.seriv.Student.main(Student.java:42)
讀出....
Exception in thread "main" java.lang.NullPointerException
    at com.it.test.seriv.Student.main(Student.java:44)

因此我們知道serialVersionUID控制了對象的版本,序列化和反序列化的時候serialVersionUID一定要一致。
當然我們也可以不寫serialVersionUID,這樣JVM會自動給我們添加上serialVersionUID的值。當類沒有任何改變的時候,我們直接序列化和反序列化是正常的,但是假如我們在序列化之後,刪除或者添加了類的某個成員,JVM就會修改serialVersionUID的值,就會導致和序列化時候的serialVersionUID不一致。這樣我們在反序列化的時候就還會報serialVersionUID不一致的錯誤。因此當我們需要序列化對象的時候,一般手動設置serialVersionUID的值。

(2)希望某個成員不被序列化

使用 transient 關鍵字修飾成員

   private transient String name;

(3)如果類中的一個成員沒實現序列化接口,我們去序列化這個類的對象會怎麼樣?

如果序列化的對象沒有包含這個成員,則正常,如果包含,則會報拋異常。
舉例
Student中包含2個構造方法,其中User成員沒有實現Serializable接口


    private  String name;
    private String sex;
    private User user;

 public Student(String name, String sex) {
        this.name = name;
        this.sex = sex;
        this.user = user;
    }
    public Student(String name, String sex, User user) {
        this.name = name;
        this.sex = sex;
        this.user = user;
    }

我們先調用第一個構造方法創建對象然後序列化

 public static void main(String[] args) {
        //序列化
        Student student =  new Student("小明","男");
        System.out.println("寫入....");
        SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");

        //反序列化
        Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
        System.out.println("讀出....");
        System.out.println(student1.toString());
    }

沒問題

寫入....
讀出....
Student{name='小明', sex='男'}

調用第二個構造方法

  public static void main(String[] args) {
        //序列化
        Student student =  new Student("小明","男",new User());
        System.out.println("寫入....");
        SerializeableUtils.saveObject(student,"D:\\xuliehuaTest\\");

        //反序列化
        Student student1 = SerializeableUtils.readObject("D:\\xuliehuaTest\\");
        System.out.println("讀出....");
        System.out.println(student1.toString());
    }

直接拋出 java.io.NotSerializableException異常,我們只需將User實現Serializable接口即可

寫入....
java.io.NotSerializableException: com.it.test.seriv.User
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at com.it.test.seriv.SerializeableUtils.saveObject(SerializeableUtils.java:42)
    at com.it.test.seriv.Student.main(Student.java:64)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.it.test.seriv.User
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1577)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at com.it.test.seriv.SerializeableUtils.readObject(SerializeableUtils.java:71)
    at com.it.test.seriv.Student.main(Student.java:67)
Caused by: java.io.NotSerializableException: com.it.test.seriv.User
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at com.it.test.seriv.SerializeableUtils.saveObject(SerializeableUtils.java:42)
    at com.it.test.seriv.Student.main(Student.java:64)
讀出....
Exception in thread "main" java.lang.NullPointerException
    at com.it.test.seriv.Student.main(Student.java:69)

(4)當一個類實現了Serializable接口,父類沒有實現Serializable接口,將子類序列化會怎麼樣?

  • 如果父類沒有定義有參的構造方法,子類的構造方法中就不需要調用其構造函數,子類可以正常序列化。只會把子類序列化。
  • 如果父類帶有有參的構造方法,子類的構造方法就需要調用父類的構造方法,這樣相當於序列化子類的時候父類也要序列化,因此父類也要實現Serializable接口。

(5)反序列化成對象之後,會調用構造函數嗎?

不會,反序列化生成對象是從二進制直接讀出來的,然後再用Object 進行強轉,不是原來的那個對象。

(6)序列化和反序列化後的對象關係?

是一個深拷貝,前後對象的引用地址不一樣

5、Android中序列化方式parcelable

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章