Java 序列化機制

一、為什麼要序列化?

1、一般情況下,只有當 JVM 處於運行時,Java 對象才可能存在,即這些對象的生命週期不會比 JVM 的生命週期更長。但在現實應用中,就可能要求在 JVM 停止運行之後能夠保存(持久化)指定的對象,並在將來重新讀取被保存的對象。Java 對象序列化就能夠幫助我們實現該功能。

2、在網絡或者進程通信中傳遞對象時,我們都需要使用序列化將 Java 對象轉換為字節序列傳輸,具體表現為:發送數據前序列化對象,接收數據後反序列化對象。

二、序列化是什麼?

序列化指的是允許將實現序列化的 Java 對象轉換位字節序列,這些字節序列可以保存在磁盤上,或通過網絡傳輸,以達到以後恢復成原來的對象。序列化機制使得對象可以脫離程序的運行而獨立存在。

通俗易懂的講,Java 序列化是指把 Java 對象轉換為字節序列的過程,而 Java 反序列化是指把字節序列恢復為 Java 對象的過程。

三、Java 序列化機制

1.使用 Serializable 接口實現序列化

在 Java 中, 只要一個類實現了 java.io.Serializable 接口,那麼它就可以被序列化。

<code>@Data
public class UserA implements Serializable {
    /**
     * 序列化ID
     */
    private static final long serialVersionUID = 1L;

    private int age;
    private static String name = "張三";

    @Override
    public String toString() {
        return "User{age=" + age + ",name=" + name + "}";
    }    
}/<code>

2.使用 Externalizable 接口實現序列化

Externalizable 繼承自 Serializable 接口,需要我們重寫 writeExternal() 與 readExternal() 方法來決定要序列化哪些信息,並且必須要提供一個 public 的無參的構造器。

Externalizable 的性能要優於 Serializable,但也增加了複雜性。

<code>@Data
public class UserB implements Externalizable {
    /**
     * 序列化ID
     */
    private static final long serialVersionUID = 1L;

    private int age;
    private static String name = "李四";


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

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "User{age=" + age + ",name=" + name + "}";
    }
}/<code>

3.序列化和反序列化

通過實現 Serializable 接口或者 Externalizable 接口,Java 對象已經具備序列化的資質了,那如何進行序列化和反序列化呢?這裡利用了 ObjectOutputStream 和 ObjectInputStream 對對象進行序列化及反序列化。

<code>    public static void main(String[] args) {
        // 序列化
        serialize();
        // 反序列化
        deserialize();
    }

    private static void serialize() {
        UserA user = new UserA();
        user.setAge(26);
        //序列化對象到文件中
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\\\\\userA"))) {
            objectOutputStream.writeObject(user);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void deserialize() {
        try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\\\\\userA"))) {
            UserA user = (UserA) objectInputStream.readObject();
            System.out.println(user);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }/<code>

四、序列化分析

  1. serialVersionUID,即序列化ID。虛擬機能否進行反序列化,不僅取決於類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致,否則反序列化時會拋出 InvalidClassException 異常。serialVersionUID 默認是 1L,可以不顯示指定。
  2. 靜態變量不會被序列化。因為靜態變量存在於虛擬機方法區,屬於全局變量,所以在序列化或者反序列時,並不會保存靜態變量。
  3. transient 關鍵字。作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化後,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。
  4. 父子類的序列化。要想將父類對象也序列化,就需要讓父類也實現 Serializable(或 Externalizable) 接口。
  5. 引用類型成員變量的序列化。要想引用對象也序列化,就需要讓引用對象也實現 Serializable(或 Externalizable) 接口。


分享到:


相關文章: