1. 程式人生 > 實用技巧 >物件序列化

物件序列化

本文源自:物件序列化為何要定義serialVersionUID的來龍去脈,因為原文不是對“序列化”的完整介紹,所以在此,結合個人理解,將“物件序列化”做一個簡要梳理!

首先,為什麼要序列化:

  1. 正常的Web專案中服務過程中,會產生”成百上千“的例項物件,而且隨著使用者訪問量的增加,物件資料量可能會越來越多,例如:session等,那麼如果這些物件都保存於服務記憶體中的話,再大的記憶體也有可能吃不消,因此,我們就需要將物件序列化到物理磁碟,需要的時候再反序列化回來。
  2. 我們知道物件本身在網路傳輸過程中是不可能直接傳輸的,需要進行轉換為二進位制檔案,才能進行正常傳送,這個轉換過程,我們就稱作序列化。

如何序列化

1.針對第一種磁碟序列化,我們需要物件實現Serializable介面,將物件以輸出流的形式暫存在File檔案中,當需要訪問它的時候,再通過輸入流寫回記憶體。下面看一個示例:

我們建立一個User物件,只有一個name屬性:

package cn.wxson.serializable;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Setter
@Getter
public class User implements Serializable {

    private String name;

    public User(String name) {
        this.name = name;
    }
}

我們建立一個User物件outUser,並把它序列化到檔案user,再將user檔案中的內容反序列化到記憶體inUser物件中。

package cn.wxson.serializable;

import lombok.extern.slf4j.Slf4j;

import java.io.*;

@Slf4j
public class SerializableTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 將物件序列化到檔案
        User outUser = new User("Tom");
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("user"));
        oo.writeObject(outUser);
        oo.close();

        // 將檔案反序列化為物件
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream("user"));
        User inUser = (User) oi.readObject();
        log.info("Name:" + inUser.getName());
        oi.close();
    }
}

2.同樣的道理,網路傳輸中的物件序列化,也要實現Serialiable介面,程式內部會自動將物件以位元組流形式寫入磁碟,然後通過網路通訊讀取磁碟資訊到記憶體進行傳輸。當然,寫入磁碟的做法是標準IO的做法,那麼NIO相較於標準IO傳輸,能夠更快的原因是,它的傳輸過程不需要物件落地,完全利用系統的虛擬記憶體技術,從Buffer緩衝區直接將序列化後的物件傳輸到網路,所以它的傳輸效率更高。

serialVersionUID的含義

我們通常在寫程式碼時,都會為實現Serialiable介面的物件增加serialVersionUID屬性。那麼,這個serialVersionUID又代表什麼含義呢?還是以上一個示例來解釋一下!

我們看到剛才的示例中,User物件並沒有serialVersionUID值。那麼,我們執行“將物件序列化到user檔案”後,在User物件中新增一個age屬性:

package cn.wxson.serializable;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Setter
@Getter
public class User implements Serializable {

    private String name;
    private int age;

    public User(String name) {
        this.name = name;
    }
}

我們再來執行“將檔案反序列化為物件”,這時就會出現以下錯誤:

Exception in thread "main" java.io.InvalidClassException: cn.wxson.serializable.User; local class incompatible: stream classdesc serialVersionUID = 4797623867994513725, local class serialVersionUID = -6015633555568763825
	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 cn.wxson.serializable.SerializableTest.main(SerializableTest.java:19)

那麼,為什麼會報錯呢?

首先,我們先來看一下報錯資訊,它的大致意思是,接收物件User中的serialVersionUID與本地檔案user中的serialVersionUID不一致導致了該錯誤。但是,我們沒有給User物件和檔案user中標識serialVersionUID啊?

其實,雖然我們沒有給User物件指定serialVersionUID屬性,但Java編譯器對User物件outUser進行序列化時,已經自動給它通過摘要演算法(類似於指紋演算法)生成了serialVersionUID,儲存在user檔案中,摘要演算法精度很高,這個serialVersionUID值是唯一的,而在我們在為物件新增age屬性後,User物件就有了一個新的serialVersionUID值,所以,利用新的serialVersionUID值來反序列化是失敗的,接收不到序列化物件。

正確做法:我們為User物件指定一個固定serialVersionUID值。

private static final long serialVersionUID = -263618247375550128L;

同樣,重複上面的步驟,先對只有name屬性的User物件inUser執行序列化操作,然後,在User中增加age屬性後,執行反序列化操作,這次就是成功的了!

希望通過本文學習,加深你對序列化的理解!