標籤:cat void pad cti 異常 debuginfo set tee 平台
一定義以及相關概念
互連網的產生帶來了機器間通訊的需求,而互聯通訊的雙方需要採用約定的協議,序列化和還原序列化屬於通訊協議的一部分。通訊協議往往採用分層模型,不同模型每層的功能定義以及顆粒度不同,例如:TCP/IP協議是一個四層協議,而OSI模型卻是七層協議模型。在OSI七層協議模型中展現層(Presentation Layer)的主要功能是把應用程式層的對象轉換成一段連續的二進位串,或者反過來,把二進位串轉換成應用程式層的對象,這兩個功能就是序列化和還原序列化。一般而言,TCP/IP協議的應用程式層對應與OSI七層協議模型的應用程式層,展示層和會話層,所以序列化協議屬於TCP/IP協議應用程式層的一部分。本文對序列化協議的講解主要基於OSI七層協議模型。
二應用情境2.1對象持久化
Java平台允許我們在記憶體中建立可複用的Java對象,但一般情況下,只有當JVM處於運行時,這些對象才可能存在,即,這些對象的生命週期不會比JVM的生命週期更長。但在現實應用中,就可能要求在JVM停止運行之後能夠儲存(持久化)指定的對象,並在將來重新讀取被儲存的對象。Java對象序列化就能夠協助我們實現該功能。在實際的生產情境中很多情況下會用到緩衝,用到redis來實現緩衝的都知道,在往緩衝裡放資料的時候不管是key還是value都要首先完成序列化,只有序列化後才能依靠redis來存放,當擷取資料的時候要還原序列化。
2.2對象網路傳輸
當在實際中使用RMI(遠程方法調用)的時候,比如hession調用,dubbo調用,或在網路中傳遞對象時,都會用到對象序列化,和還原序列化。
三如何?序列化
在Java中,只要一個類實現了java.io.Serializable介面,那麼它就可以被序列化。首先定義一個User類。
1package com.duomeng.product;
2import java.io.*;
3public class User implements Serializable{
4 private static final long serialVersionUID = -6513548580766215238L;
5 public String name;
6 public String pawssWord;
7 public String getName() {
8 return name;
9 }
10 public void setName(String name) {
11 this.name = name;
12 }
13 public String getPawssWord() {
14 return pawssWord;
15 }
16 public void setPawssWord(String pawssWord) {
17 this.pawssWord = pawssWord;
18 }
19}
接著寫一個測試類別來完成序列化和還原序列化的測試,就定義為UserTest.class
1package com.duomen.product;
2import java.io.*;
3public class UserTest {
4 public static void main(String[] args) {
5 User user = new User();
6 user.setName("duomeng");
7 user.setPawssWord("123qwe");
8 try {
9 //序列化
10 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("user.obj"));
11 objectOutputStream.writeObject(user);
12 objectOutputStream.flush();
13 objectOutputStream.close();
14 //還原序列化
15 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("user.obj"));
16 User oldUser = (User) objectInputStream.readObject();
17 objectInputStream.close();
18 System.out.println("name = [" + oldUser.getName() + "]");
19 System.out.println("password = [" + oldUser.getPawssWord() + "]");
20 } catch (IOException e) {
21 e.printStackTrace();
22 } catch (ClassNotFoundException e) {
23 e.printStackTrace();
24 }
25 }
26}
如果沒有什麼的問題的,那麼結果也一定不出所料的輸出如下內容
1name = [duomeng]
2password = [123qwe]
四相關知識
1.通過ObjectOutputStream和ObjectInputStream對對象進行序列化及還原序列化
2.一個類如果被執行個體化,那麼這個類必須實現java.io.Serializable,否在在執行個體化的時候會拋出java.io.NotSerializableException異常,因為在序列化的過程中大概有如下的調用列writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject,可以看下一下writeObject0方法裡的部分代碼,如下
1// remaining cases
2 if (obj instanceof String) {
3 writeString((String) obj, unshared);
4 } else if (cl.isArray()) {
5 writeArray(obj, desc, unshared);
6 } else if (obj instanceof Enum) {
7 writeEnum((Enum<?>) obj, desc, unshared);
8 } else if (obj instanceof Serializable) {
9 writeOrdinaryObject(obj, desc, unshared);
10 } else {
11 if (extendedDebugInfo) {
12 throw new NotSerializableException(
13 cl.getName() + "\n" + debugInfoStack.toString());
14 } else {
15 throw new NotSerializableException(cl.getName());
16 }
17 }
3.虛擬機器是否允許還原序列化,不僅取決於類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)如果不顯示的指定序列化Id,那麼在序列化的過程中jvm會根據當前的對象的類的屬性和方法計算出一個serialVersionUID,同樣當還原序列化的時候也會根據目標類的屬性和方法結算一個serialVersionUID,如果在序列化之後,對應的類的屬性或者方法發生變化那麼計算出來的serialVersionUID是不一樣的,這樣在還原序列化的時候就會拋異常,還原序列化失敗,通常的做法是通過IDE來顯示的產生,這樣不僅可以防止修改類的屬性或者方法導致還原序列化失敗,從而也可以依靠serialVersionUID的不同來對一些訪問做限制,比如變化了serialVersionUID之後,那些不知道最新serialVersionUID的訪問將會失敗。
4.要想將父類對象也序列化,就需要讓父類也實現java.io.Serializable介面,否則父類的屬性並不能完成序列化。
五總結
在實際的應用當中序列化的實現還有很多,比如通常用到的json和xml也同樣是一種序列化,就像開頭所說的只要是將資料結構或對象轉換成二進位串的過程就叫序列化,只要是將在序列化過程中所產生的二進位串轉換成資料結構或者對象的過程就是還原序列化。
Java序列化系列教程(上)