物件序列化
本文源自:物件序列化為何要定義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
屬性後,執行反序列化操作,這次就是成功的了!
希望通過本文學習,加深你對序列化的理解!