Java的序列化和反序列化

weixin_33806914發表於2019-04-06

Java的序列化和反序列化
概述
Java物件的序列化和反序列化,這個詞對我來說追溯到大學階段,學Java物件流時知道有這東西。老師告訴我們可以把Java物件化作位元組流,儲存檔案或網路通訊。然後就是巴啦巴拉,一臉懵逼。舉個例子,有一臺北京的Java虛擬機器現在執行的某個物件要呼叫一臺在長春執行的Java虛擬機器內的某個物件,這是兩個不同的Java虛擬機器程式,我們沒辦法直接傳遞物件的引用,現在我們只能把長春的這個物件序列化,變成一塊一塊碎片,傳給北京的虛擬機器,北京虛擬機器反序列化後就造出了一個物件,然後就可以正常使用。說得通俗點,這個序列化就是跨程式資料傳輸。

序列化(Serializable介面)
要序列化的類通過實現java.io.Serializable介面啟動序列化的功能,如果它有子類,所有的子類本身也都可序列化。

Person類

public class Person implements Serializable
{

private String name;

private int age;

private String sex;

public Person(String name,int age,String sex)
{
    this.name = name;
    this.age = age;
    this.sex = sex;
}

public int getAge()
{
    return age;
}

public String getName()
{
    return name;
}
public String getSex()
{
    return sex;
}

@Override
public String toString()
{
    return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}

}
serializable介面沒有函式或者欄位,我們可以看到我們implements介面,沒實現任何的函式,它僅僅用於標識可序列化,如果我們沒有實現這個標識介面而進行序列化,會丟擲一個NotSerializableException異常。

Main類

public class Main
{

public static void main(String[] args)
{
    serializePerson();
}
private static void serializePerson()
{
    try {
        Person customer = new Person("張三",15,"男");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:/Person"));
        objectOutputStream.writeObject(customer);
        System.out.println("Person序列化完成。。。");
        objectOutputStream.close();
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("Person序列化出錯。。。");
    }
}

}
輸出

Person序列化完成。。
在 E盤下就會有一個Person檔案,用notepad++開啟,依稀可以見到一些熟悉的字眼

我們用二進位制檢視器開啟這個檔案

左邊第一個部分是序列化的檔案頭AC ED 00 05,其他還有關於序列化的類描述,裡面的各個屬性值,還有父類的資訊,lz實在看不懂了,有大佬分析過序列化檔案,有興趣可自行百度檢視。

反序列化
Main類新增DeserializePerson函式

private static Object DeserializePerson()
{
    ObjectInputStream objectInputStream = null;
    try {
        objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:/Person")));
        Object object = objectInputStream.readObject();
        System.out.println("反序列化完成。。。");
        return object;
    } catch (Exception e)
    {
        e.printStackTrace();
    }finally
    {
        if (objectInputStream != null )
        {
            try
            {
                objectInputStream.close();
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
    return null;
}

輸出

反序列化完成。。。
姓名:張三,年齡:15,性別:男
serialVersionUID(標識)
知道serializable是標識的語義,這個標識是在哪?如果我們沒特意指定,在編譯過程中Java編譯器會預設賦予它一個獨一無二的編號,保證它是唯一的。但這樣做是否會給我們帶來影響?

Person類

public class Person implements Serializable
{

private String name;

private int age;

private String sex;

   //新增了一個屬性

private String number;

public Person(String name,int age,String sex)
{
    this.name = name;
    this.age = age;
    this.sex = sex;
}

public int getAge()
{
    return age;
}

public String getName()
{
    return name;
}
public String getSex()
{
    return sex;
}

@Override
public String toString()
{
    return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}

}
Main類

public class Main
{

public static void main(String[] args)
{
    //serializePerson();
    Person person = (Person) DeserializePerson();
    System.out.println(person);
}

private static Object DeserializePerson()
{
    ObjectInputStream objectInputStream = null;
    try {
        objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:/Person")));
        Object object = objectInputStream.readObject();
        System.out.println("反序列化完成。。。");
        return object;
    } catch (Exception e)
    {
        e.printStackTrace();
    }finally
    {
        if (objectInputStream != null )
        {
            try
            {
                objectInputStream.close();
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
    return null;
}

private static void serializePerson()
{
    try {
        Person customer = new Person("張三",15,"男");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:/Person"));
        objectOutputStream.writeObject(customer);
        System.out.println("Person序列化完成。。。");
        objectOutputStream.close();
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("Person序列化出錯。。。");
    }
}

}
發現丟擲了一個異常

本地的檔案流中的class(序列化)和修改完的Person.class,不相容了(UID),處於安全機制考慮,程式丟擲錯誤,拒絕載入。如何保證UID版本一致,那隻能自己指定UID,在序列化後,去新增欄位或者函式,就不會影響後期還原。

Person類

public class Person implements Serializable
{

private static final long serialVersionUID = 55555L;

private String name;

private int age;

private String sex;

public Person(String name,int age,String sex)
{
    this.name = name;
    this.age = age;
    this.sex = sex;
}

public int getAge()
{
    return age;
}

public String getName()
{
    return name;
}
public String getSex()
{
    return sex;
}

@Override
public String toString()
{
    return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}

}
Main類

public class Main
{

public static void main(String[] args)
{
    serializePerson();
    Person person = (Person) DeserializePerson();
    System.out.println(person);
}

private static Object DeserializePerson()
{
    ObjectInputStream objectInputStream = null;
    try {
        objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:/Person")));
        Object object = objectInputStream.readObject();
        System.out.println("反序列化完成。。。");
        return object;
    } catch (Exception e)
    {
        e.printStackTrace();
    }finally
    {
        if (objectInputStream != null )
        {
            try
            {
                objectInputStream.close();
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
    return null;
}

private static void serializePerson()
{
    try {
        Person customer = new Person("張三",15,"男");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:/Person"));
        objectOutputStream.writeObject(customer);
        System.out.println("Person序列化完成。。。");
        objectOutputStream.close();
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("Person序列化出錯。。。");
    }
}

}
序列化後,我們註釋掉main函式裡的serializePerson();修改Person類,新增或修改欄位,進行反序列化。

反序列化完成。。。
姓名:張三,年齡:15,性別:男
我們可以發現,由編譯器預設自動給我們生成的UID編碼,並不可控,對同一個類,A編譯器編譯,賦予一個UID的值和B編譯器編譯賦予的UID值也有可能不同,所以為了提高可控性,確定性,我們在一個可序列化的類中應該明確為它賦值。

Externalizable介面
以上我們可以發現,所有的序列化操作都是預設的,自動幫我們完成。但有時我們並不想這樣,有些屬性我們並不想序列化,想要自定義的方式去序列化它。為此,Java提供了一個Externalizable介面,方便使用者自定義序列化過程,它和Serializable有什麼區別?

Person類

public class Person implements Externalizable
{

private static final long serialVersionUID = 55555L;

private String name;

private int age;

private String sex;

public Person()
{

}

public Person(String name,int age,String sex)
{
    this.name = name;
    this.age = age;
    this.sex = sex;
}

public int getAge()
{
    return age;
}

public String getName()
{
    return name;
}
public String getSex()
{
    return sex;
}

@Override
public String toString()
{
    return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}

@Override
public void writeExternal(ObjectOutput out) throws IOException
{

}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{

}

}
Main類不變,輸出

Person序列化完成。。。
反序列化完成。。。
姓名:null,年齡:0,性別:null
與serialization介面相比,我們很快就能看出,Externalizable介面對Person類進行序列化和反序列化之後得到的物件的狀態並沒有儲存下來,所有屬性的值都變成預設值。它們之間有什麼關係和區別?

Externalizable繼承了Serializable,它定義了兩個抽象函式,writeExternal和readExternal,我們進行序列化和反序列需要重寫,可以指定序列化哪些屬性。
Externalizable序列化的類必須有一個無參建構函式,否則會報錯。因為Externalizable序列化的時候,讀取物件時,會呼叫無參建構函式建立一個新的物件,之後將儲存物件的欄位的值填充到新物件中。
修改Person類,重新序列化

@Override
public void writeExternal(ObjectOutput out) throws IOException
{
    out.writeObject(name);
    out.writeObject(age);
    out.writeObject(sex);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
    this.name = (String)in.readObject();
    this.age = (int)in.readObject();
    this.sex = (String)in.readObject();
}

輸出

Person序列化完成。。。
反序列化完成。。。
姓名:張三,年齡:15,性別:男
重寫完兩個函式,發現物件持久化完成。但細心的小夥伴可能會發現,我們序列化的成員變數都是例項變數。就會有一個疑問,換成靜態變數試試?

靜態變數被序列化?
其實序列化(預設序列化)被不儲存靜態變數,因為靜態變數屬於類本身,物件序列化,顧名思義就是指的物件本身狀態,並不包含靜態變數。

public class Person implements Serializable
{

private static final long serialVersionUID = 55555L;

private String name;

private int age;

private String sex;

private static String money;

public Person(String name,int age,String sex,String money)
{
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.money = money;
}

public int getAge()
{
    return age;
}

public String getName()
{
    return name;
}
public String getSex()
{
    return sex;
}

@Override
public String toString()
{
    return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex + ",資產:" + money;
}

}
Main類

public class Main {

public static void main(String[] args) throws Exception {
    serializablePerson();
    Person person = (Person)DeserializablePerson();
    System.out.println(person);

}
//演示使用,並不規範
private static void serializablePerson() throws Exception {
    Person person = new Person("張三",15,"男","5000000");
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/Person"));
    objectOutputStream.writeObject(person);
    objectOutputStream.close();
    System.out.println("序列化完成。。。");
}

private static Object DeserializablePerson() throws Exception
{
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
    Object object = objectInputStream.readObject();
    objectInputStream.close();
    System.out.println("反序列完成。。。");
    return object;
}

}
輸出

序列化完成。。。
反序列完成。。。
姓名:張三,年齡:15,性別:男,資產:5000000
結果跟我們的結論出乎意料,靜態變數被序列化了,真的是這樣嗎?導致這個原因是因為我們測試都是在一個程式裡面的。JVM把money這個變數載入進來了,所以導致我們看到的是載入過的money。我們可以這樣做,多寫一個Main類,讓JVM退出後,重新載入。

MainTest類

public class MainTest {

public static void main(String[] args) throws Exception {
    Person person = (Person)DeserializablePerson();
    System.out.println(person);
}
private static Object DeserializablePerson() throws Exception
{
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
    Object object = objectInputStream.readObject();
    objectInputStream.close();
    System.out.println("反序列化完成。。。");
    return object;
}

}
現在先執行Main類,得到的是我們上面的介面,現在執行MainTest類

反序列化完成。。。
姓名:張三,年齡:15,性別:男,資產:null
可以發現靜態成員變數並沒有被儲存下來,變成一個預設值。

transient關鍵字(預設序列化)
有時候我們並不想自定義序列化,然而有些成員變數我們也不想序列化。那麼transient這個關鍵字就是你的不二人選,它的作用很簡單,就是控制變數的序列化,在變數宣告前加上這個關鍵字即可。

Person類

public class Person implements Serializable
{

private static final long serialVersionUID = 55555L;

private String name;

private int age;

private String sex;

private static String money;
//銀行賬戶
private transient String bankNumber;
//銀行密碼
private transient int passWord;

public Person(String name,int age,String sex,String money,String bankNumber,int passWord)
{
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.money = money;
    this.bankNumber = bankNumber;
    this.passWord = passWord;
}

public int getAge()
{
    return age;
}

public String getName()
{
    return name;
}
public String getSex()
{
    return sex;
}

@Override
public String toString()
{
    return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex + ",資產:" + money + ",我的銀行賬戶是:" + this.bankNumber + ",我的銀行密碼:" +  this.passWord;
}

}
Main類

public class Main {

public static void main(String[] args) throws Exception {
    serializablePerson();
    Person person = (Person)DeserializablePerson();
    System.out.println(person);

}
//演示使用,並不規範
private static void serializablePerson() throws Exception {
    Person person = new Person("張三",15,"男","5000000","564654979797464646",123456);
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/Person"));
    objectOutputStream.writeObject(person);
    objectOutputStream.close();
    System.out.println("序列化完成。。。");
}

private static Object DeserializablePerson() throws Exception
{
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
    Object object = objectInputStream.readObject();
    objectInputStream.close();
    System.out.println("反序列完成。。。");
    return object;
}

}
輸出

序列化完成。。。
反序列完成。。。
姓名:張三,年齡:15,性別:男,資產:5000000,我的銀行賬戶是:null,我的銀行密碼:0
這些關鍵資訊就都不會被序列化到檔案中,當然我有500w的話

序列化的儲存規則
Java序列化為了節省儲存空間,有特定的儲存規則,寫入檔案為同一物件的時候,並不會再將物件的內容儲存,而只是再次儲存一份引用。

Main類

public class Main {

public static void main(String[] args) throws Exception {
    serializablePerson();
    Person person = (Person) DeserializablePerson();
    System.out.println(person);

}
//演示使用,並不規範
private static void serializablePerson() throws Exception {
    Person person = new Person("張三",15,"男","5000000","564654979797464646",123456);
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/Person"));
    objectOutputStream.writeObject(person);
    objectOutputStream.flush();
    System.out.println(new File("D:/Person").length());
    objectOutputStream.writeObject(person);
    objectOutputStream.close();
    System.out.println(new File("D:/Person").length());
    System.out.println("序列化完成。。。");
}

private static Object DeserializablePerson() throws Exception
{
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
    Person person =(Person) objectInputStream.readObject();
    Person person1 =(Person) objectInputStream.readObject();
    objectInputStream.close();
    System.out.println("反序列完成。。。");
    System.out.print("是否同一個物件=====>");
    System.out.println(person == person1);
    return person;
}

}
輸出

108
113
序列化完成。。。
反序列完成。。。
是否同一個物件=====>true
姓名:張三,年齡:15,性別:男,資產:5000000,我的銀行賬戶是:null,我的銀行密碼:0
多出五位元組儲存空間就是新增引用和一些控制資訊空間,反序列時,恢復引用關係,person和person1都指向唯一的物件,二者相等,輸出true,這樣的儲存規則就極大節省了儲存的空間。

注意事項
可以發現,我很多地方加了預設序列化的情況下,如果是自定義序列化,那麼transient這些就統統無效,是不是感覺可控性增強不少,序列化還得注意幾個點。

如果有內部類,或者是要序列化的物件的成員變數是一個物件類,那麼也必須繼承序列化的介面,否則會出錯滴。
子類即使沒有實現序列化的介面,只要父類實現了,那子類就可以直接序列化。
參考:Java序列化的高階認識

===============================================================

如發現錯誤,請及時留言,lz及時修改,避免誤導後來者。感謝!!!
原文地址https://www.cnblogs.com/dslx/p/10648414.html

相關文章