Java對象的序列化與反序列化

序列化與反序列化

序列化 (Serialization)是將對象的狀態信息轉換爲能夠存儲或傳輸的形式的過程。通常將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩衝等。在網絡傳輸過程當中,能夠是字節或是XML格式。而字節的或XML編碼格式能夠還原徹底相等的對象。這個相反的過程又稱爲反序列化java

Java對象的序列化與反序列化

在Java中,咱們能夠經過多種方式來建立對象,而且只要對象沒有被回收咱們均可以複用該對象。可是,咱們建立出來的這些Java對象都是存在於JVM的堆內存中的。只有JVM處於運行狀態的時候,這些對象纔可能存在。一旦JVM中止運行,這些對象的狀態也就隨之而丟失了。git

可是在真實的應用場景中,咱們須要將這些對象持久化下來,而且可以在須要的時候把對象從新讀取出來。Java的對象序列化能夠幫助咱們實現該功能。github

對象序列化機制(object serialization)是Java語言內建的一種對象持久化方式,經過對象序列化,能夠把對象的狀態保存爲字節數組,而且能夠在有須要的時候將這個字節數組經過反序列化的方式再轉換成對象。對象序列化能夠很容易地在JVM中的活動對象字節數組(流)之間進行轉換。apache

在Java中,對象的序列化與反序列化被普遍應用到RMI(遠程方法調用)及網絡傳輸中。數組

相關接口及類

Java爲了方便開發人員將Java對象進行序列化及反序列化而提供了一套方便的API來支持。其中包括如下接口和類:網絡

java.io.Serializableide

java.io.Externalizable函數

ObjectOutput工具

ObjectInputui

ObjectOutputStream

ObjectInputStream

Serializable 接口

類經過實現 java.io.Serializable 接口以啓用其序列化功能。未實現此接口的類將沒法使其任何狀態序列化或反序列化。可序列化類的全部子類型自己都是可序列化的。序列化接口沒有方法或字段,僅用於標識可序列化的語義。 (該接口並無方法和字段,爲何只有實現了該接口的類的對象才能被序列化呢?)

當試圖對一個對象進行序列化的時候,若是遇到不支持Serializable接口的對象。在此狀況下,將拋出 NotSerializableException

若是要序列化的類有父類,要想同時將在父類中定義過的變量持久化下來,那麼父類也應該集成java.io.Serializable接口。

下面是一個實現了java.io.Serializable接口的類

package com.hollischaung.serialization.SerializableDemos;import java.io.Serializable;
/**
 * Created by hollis on 16/2/17.
 * 實現Serializable接口
 */
public class User1 implements Serializable {
    
    private String name;
    private int age;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' + 
                ", age=" + age +
        '}';
    }}

經過下面的代碼進行序列化及反序列化

package com.hollischaung.serialization.SerializableDemos;
import org.apache.commons.io.FileUtils;import org.apache.commons.io.IOUtils;import java.io.*;
/**
 * Created by hollis on 16/2/17.
 * SerializableDemo1 結合SerializableDemo2說明 一個類要想被序列化必須實現Serializable接口
 */
public class SerializableDemo1 {

    public static void main(String[] args) {
        //Initializes The Object
        User1 user = new User1();
        user.setName("hollis");
        user.setAge(23);
        System.out.println(user);

        //Write Obj to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(oos);
        }

        //Read Obj from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            User1 newUser = (User1) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ois);
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}//OutPut://User{name='hollis', age=23}//User{name='hollis', age=23}

更多關於Serializable的使用,請參考代碼實例

Externalizable接口

除了Serializable 以外,java中還提供了另外一個序列化接口Externalizable

爲了瞭解Externalizable接口和Serializable接口的區別,先來看代碼,咱們把上面的代碼改爲使用Externalizable的形式。

package com.hollischaung.serialization.ExternalizableDemos;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
 * Created by hollis on 16/2/17.
 * 實現Externalizable接口
 */
public class User1 implements Externalizable {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    public void writeExternal(ObjectOutput out) throws IOException {

    }

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

    }

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

 

package com.hollischaung.serialization.ExternalizableDemos;import java.io.*;
/**
 * Created by hollis on 16/2/17.
 */public class ExternalizableDemo1 {

//爲了便於理解和節省篇幅,忽略及。真正編碼時千萬不要忘記
//IOException直接拋出
public static void main(String[] args) throws IOException, ClassNotFoundException {
    //Write Obj to file
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
    User1 user = new User1();
    user.setName("hollis");
    user.setAge(23);
    oos.writeObject(user);
    //Read Obj from file
    File file = new File("tempFile");
    ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
    User1 newInstance = (User1) ois.readObject();
    //output
    System.out.println(newInstance);
    }
}//OutPut://User{name='null', age=0}

經過上面的實例能夠發現,對User1類進行序列化及反序列化以後獲得的對象的全部屬性的值都變成了默認值。也就是說,以前的那個對象的狀態並無被持久化下來。這就是Externalizable接口和Serializable接口的區別:

Externalizable繼承了Serializable,該接口中定義了兩個抽象方法:writeExternal()readExternal()。當使用Externalizable接口來進行序列化與反序列化的時候須要開發人員重寫writeExternal()readExternal()方 法。因爲上面的代碼中,並無在這兩個方法中定義序列化實現細節,因此輸出的內容爲空。還有一點值得注意:在使用Externalizable進行序列化的時候,在讀取對象時,會調用被序列化類的無參構造器去建立一個新的對象,而後再將被保存對象的字段分別填充到新對象中。因此,實現Externalizable接口的類必需要提供一個public的無參的構造器。

按照要求修改以後代碼以下:

package com.hollischaung.serialization.ExternalizableDemos;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
 * Created by hollis on 16/2/17.
 * 實現Externalizable接口,並實現writeExternal和readExternal方法
 */
public class User2 implements Externalizable {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

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

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }

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

 

package com.hollischaung.serialization.ExternalizableDemos;import java.io.*;
/**
 * Created by hollis on 16/2/17.
 */
public class ExternalizableDemo2 {

    //爲了便於理解和節省篇幅,忽略關閉流操做及刪除文件操做。真正編碼時千萬不要忘記
    //IOException直接拋出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        User2 user = new User2();
        user.setName("hollis");
        user.setAge(23);
        oos.writeObject(user);
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        User2 newInstance = (User2) ois.readObject();
        //output
        System.out.println(newInstance);
    }
}//OutPut://User{name='hollis', age=23}

此次,就能夠把以前的對象狀態持久化下來了。

若是User類中沒有無參數的構造函數,在運行時會拋出異常:java.io.InvalidClassException

更多Externalizable接口使用實例請參考代碼實例

ObjectOutput和ObjectInput 接口

ObjectInput接口擴展自DataInput接口以包含對象的讀操做。

DataInput接口用於從二進制流中讀取字節,並根據全部Java基本類型數據進行重構。同時還提供根據UTF-8修改版格式的數據重構String的工具。

對於此接口中的全部數據讀取例程來講,若是在讀取所需字節數以前已經到達文件末尾 (end of file),則將拋出 EOFException(IOException 的一種)。若是由於到達文件末尾之外的其餘緣由沒法讀取字節,則將拋出 IOException而不是EOFException。尤爲是,在輸入流已關閉的狀況下,將拋出IOException

ObjectOutput擴展DataOutput接口以包含對象的寫入操做。

DataOutput 接口用於將數據從任意 Java 基本類型轉換爲一系列字節,並將這些字節寫入二進制流。同時還提供了一個將 String 轉換成 UTF-8 修改版格式並寫入所獲得的系列字節的工具。

對於此接口中寫入字節的全部方法,若是因爲某種緣由沒法寫入某個字節,則拋出 IOException。

ObjectOutputStream類和ObjectInputStream類

經過前面的代碼片斷中咱們也能知道,咱們通常使用ObjectOutputStream的writeObject方法把一個對象進行持久化。再使用ObjectInputStream的readObject持久化存儲中把對象讀取出來。

更多關於ObjectInputStream和ObjectOutputStream的相關知識歡迎閱讀個人另外兩篇博文:深刻分析Java的序列化與反序列化單例與序列化的那些事兒

Transient關鍵字

Transient關鍵字的做用是控制變量的序列化,在變量聲明前加上該關鍵字,能夠阻止該變量被序列化到文件中,在被反序列化後,transient變量的值被設爲初始值,如int型的是0,對象型的是null。關於Transient關鍵字的拓展知識歡迎閱讀深刻分析Java的序列化與反序列化

序列化ID

虛擬機是否容許反序列化,不只取決於類路徑和功能代碼是否一致,一個很是重要的一點是兩個類的序列化ID是否一致(就是 private static final long serialVersionUID)

序列化ID在Eclipse下提供了兩種生成策略,一個是固定的1L,一個是隨機生成一個不重複的long類型數據(其實是使用JDK工具生成),在這裏有一個建議,若是沒有特殊需求,就是用默認的1L就能夠,這樣能夠確保代碼一致時反序列化成功。那麼隨機生成的序列化ID有什麼做用呢,有些時候,經過改變序列化ID能夠用來限制某些用戶的使用。

相關文章
相關標籤/搜索