對象序列化
- 2020 年 8 月 7 日
- 筆記
- JAVA, Serializable, 後端, 序列化
本文源自:對象序列化為何要定義serialVersionUID的來龍去脈,因為原文不是對「序列化」的完整介紹,所以在此,結合個人理解,將「對象序列化」做一個簡要梳理!
首先,為什麼要序列化:
- 正常的Web項目中服務過程中,會產生」成百上千「的實例對象,而且隨著用戶訪問量的增加,對象數據量可能會越來越多,例如:session等,那麼如果這些對象都保存於服務記憶體中的話,再大的記憶體也有可能吃不消,因此,我們就需要將對象序列化到物理磁碟,需要的時候再反序列化回來。
- 我們知道對象本身在網路傳輸過程中是不可能直接傳輸的,需要進行轉換為二進位文件,才能進行正常傳送,這個轉換過程,我們就稱作序列化。
如何序列化
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
屬性後,執行反序列化操作,這次就是成功的了!
希望通過本文學習,加深你對序列化的理解!