java序列化與反序列化

序列化和反序列化的概念

1、把對象轉換爲字節序列的過程稱爲對象的序列化

2、把字節序列恢復爲對象的過程稱爲對象的反序列化

對象的序列化主要有兩種用途:

1) 把對象的字節序列永久地保存到硬盤上,通常存放在一個文件中;

2) 在網絡上傳送對象的字節序列。

當兩個進程在進行遠程通信時,彼此可以發送各種類型的數據。無論是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方需要把這個Java對象轉換爲字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復爲Java對象。

舉個真實的案例:當我們需要使用的對象很複雜或者需要很長時間去構造,這時就會引入使用代理模式(Proxy)。例如:如果構建一個對象很耗費時間和計算機資源,代理模式(Proxy)允許我們控制這種情況,直到我們需要使用實際的對象。一個代理(Proxy)通常包含和將要使用的對象同樣的方法,一旦開始使用這個對象,這些方法將通過代理(Proxy)傳遞給實際的對象。

解讀:在微服務化盛行的今天,很多複雜的對象構造起來比較耗時,爲了節省開支,某些公司將這部分複雜的對象先圈起來,寫成服務起在遠端B,並在調用端A端以代理(Proxy)的形式提供對服務的訪問,這期間從B到A遠程調的過程形成了Java對象序列化和反序列化的相關操作!

jdk類庫中序列化api

java.io.ObjectOutputStream

代表對象輸出流,它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。

java.io.ObjectInputStream

代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化爲一個對象,並將其返回。

只有實現了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自 Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行爲,而僅實現Serializable接口的類可以 採用默認的序列化方式

對象序列化包括如下步驟:

1) 創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,如文件輸出流;

2) 通過對象輸出流的writeObject()方法寫對象。

對象反序列化的步驟如下:

1) 創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,如文件輸入流;

2) 通過對象輸入流的readObject()方法讀取對象。

java代碼實現:

package com.awakeyo.serializable;

import java.io.Serializable;

/**
 * @author awakeyoyoyo
 * @className Person
 * @description TODO
 * @date 2020-03-31 13:53
 */
public class Person implements Serializable {
    private static final long serialVersionUID=13316311153L;
    private int age;
    private String name;
    private String sex;

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    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;
    }
}

package com.awakeyo.serializable;

import com.sun.org.apache.bcel.internal.generic.NEW;

import java.io.*;

/**
 * @author awakeyoyoyo
 * @className SerializePersonTest
 * @description TODO
 * @date 2020-03-31 13:57
 */
public class SerializePersonTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person=new Person();
        person.setName("lqhao");
        person.setAge(21);
        person.setSex("男");
        ObjectOutputStream oo=new ObjectOutputStream(new FileOutputStream(new File("./person.txt")));
        oo.writeObject(person);
        System.out.println("Person對象序列化成果");
        oo.close();
        System.out.println("--------------------");
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("./person.txt"));
        Person person1=(Person) ois.readObject();
        System.out.println("Persion反序列化成果");
        System.out.println(person1.toString());
        ois.close();
    }

}

運行結果
在這裏插入圖片描述
serialVersionUID的作用

serialVersionUID適用於Java的序列化機制。簡單來說,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException。

具體的序列化過程是這樣的:序列化操作的時候系統會把當前類的serialVersionUID寫入到序列化文件中,當反序列化時系統會去檢測文件中的serialVersionUID,判斷它是否與當前類的serialVersionUID一致,如果一致就說明序列化類的版本與當前類版本是一樣的,可以反序列化成功,否則失敗。

serialVersionUID有兩種顯示的生成方式:
一是默認的1L,比如:private static final long serialVersionUID = 1L;
二是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;

當實現java.io.Serializable接口的類沒有顯式地定義一個serialVersionUID變量時候,Java序列化機制會根據編譯的Class自動生成一個serialVersionUID作序列化版本比較用,這種情況下,如果Class文件(類名,方法明等)沒有發生變化(增加空格,換行,增加註釋等等),就算再編譯多次,serialVersionUID也不會變化的

靜態變量序列化

修改代碼

package com.awakeyo.serializable;

import java.io.Serializable;

/**
 * @author awakeyoyoyo
 * @className Person
 * @description TODO
 * @date 2020-03-31 13:53
 */
public class Person implements Serializable {
    private static final long serialVersionUID=13316311153L;
    private int age;
    private String name;
    private String sex;
    public static int staticVar = 10;

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    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;
    }
}

package com.awakeyo.serializable;

import com.sun.org.apache.bcel.internal.generic.NEW;

import java.io.*;

/**
 * @author awakeyoyoyo
 * @className SerializePersonTest
 * @description TODO
 * @date 2020-03-31 13:57
 */
public class SerializePersonTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person=new Person();
        person.setName("lqhao");
        person.setAge(21);
        person.setSex("男");
        ObjectOutputStream oo=new ObjectOutputStream(new FileOutputStream(new File("./person.txt")));
        oo.writeObject(person);
        System.out.println("Person對象序列化成果");
        oo.close();
        System.out.println("--------------------");
        Person.staticVar=999;
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("./person.txt"));
        Person person1=(Person) ois.readObject();
        System.out.println("Persion反序列化成果");
        System.out.println(person1.toString());
        System.out.println("staticvar="+person1.staticVar);
        ois.close();
    }

}

最終結果在這裏插入圖片描述
最後的輸出是 999,打印的 staticVar 是從讀取的對象裏獲得的,應該是保存時的狀態纔對。之所以打印 999的原因在於序列化時,並不保存靜態變量,這其實比較容易理解,序列化保存的是對象的狀態,靜態變量屬於類的狀態,因此 序列化並不保存靜態變量。

父類的序列化與 Transient 關鍵字

父類的序列化

要想將父類對象也序列化,就需要讓父類也實現Serializable 接口。如果父類不實現的話的,就 需要有默認的無參的構造函數。在父類沒有實現 Serializable 接口時,虛擬機是不會序列化父對象的,而一個 Java 對象的構造必須先有父對象,纔有子對象,反序列化也不例外。所以反序列化時,爲了構造父對象,只能調用父類的無參構造函數作爲默認的父對象。因此當我們取父對象的變量值時,它的值是調用父類無參構造函數後的值。如果你考慮到這種序列化的情況,在父類無參構造函數中對變量進行初始化,否則的話,父類變量值都是默認聲明的值,如 int 型的默認是 0,string 型的默認是 null。

Transient 關鍵字

作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化後,transient 變量的值被設爲初始值,如 int 型的是 0,對象型的是 null。

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