標籤:read extends 需要 類型 檢查 建議 聲明 長度 com
Java序列化演算法透析
Serialization(序列化)是一種將對象以一連串的位元組描述的過程;還原序列化deserialization是一種將這些位元組重建成一個對象的過程。Java序列化API提供一種處理對象序列化的標準機制。在這裡你能學到如何序列化一個對象,什麼時候需要序列化以及Java序列化的演算法,我們用一個執行個體來示範序列化以後的位元組是如何描述一個對象的資訊的。
序列化的必要性
Java中,一切都是對象,在分布式環境中經常需要將Object從這一端網路或裝置傳遞到另一端。
這就需要有一種可以在兩端傳輸資料的協議。Java序列化機制就是為瞭解決這個問題而產生。
如何序列化一個對象
一個對象能夠序列化的前提是實現Serializable介面,Serializable介面沒有方法,更像是個標記。
有了這個標記的Class就能被序列化機制處理。
import java.io.Serializable;
class TestSerial implements Serializable {
public byte version = 100;
public byte count = 0;
}
然後我們寫個程式將對象序列化並輸出。ObjectOutputStream能把Object輸出成Byte流。
我們將Byte流暫時儲存到temp.out檔案裡。
public static void main(String args[]) throws IOException {
FileOutputStream fos = new FileOutputStream("temp.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
TestSerial ts = new TestSerial();
oos.writeObject(ts);
oos.flush();
oos.close();
}
如果要從持久的檔案中讀取Bytes重建對象,我們可以使用ObjectInputStream。
public static void main(String args[]) throws IOException {
FileInputStream fis = new FileInputStream("temp.out");
ObjectInputStream oin = new ObjectInputStream(fis);
TestSerial ts = (TestSerial) oin.readObject();
System.out.println("version="+ts.version);
}
執行結果為 100.
對象的序列化格式
將一個對象序列化後是什麼樣子呢?開啟剛才我們將對象序列化輸出的temp.out檔案
以16進位方式顯示。內容應該如下:
AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 6573 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 0563 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 7870 00 64
這一坨位元組就是用來描述序列化以後的TestSerial對象的,我們注意到TestSerial類中只有兩個域:
public byte version = 100;
public byte count = 0;
且都是byte型,理論上儲存這兩個域只需要2個byte,但是實際上temp.out佔據空間為51bytes,也就是說除了資料以外,還包括了對序列化對象的其他描述
Java的序列化演算法
序列化演算法一般會按步驟做如下事情:
◆將對象執行個體相關的類別中繼資料輸出。
◆遞迴地輸出類的超類描述直到不再有超類。
◆類別中繼資料完了以後,開始從最頂層的超類開始輸出對象執行個體的實際資料值。
◆從上至下遞迴輸出執行個體的資料
我們用另一個更完整覆蓋所有可能出現的情況的例子來說明:
class parent implements Serializable {
int parentVersion = 10;
}
class contain implements Serializable{
Int containVersion = 11;
}
public class SerialTest extends parent implements Serializable {
int version = 66;
contain con = new contain();
public int getVersion() {
return version;
}
public static void main(String args[]) throws IOException {
FileOutputStream fos = new FileOutputStream("temp.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerialTest st = new SerialTest();
oos.writeObject(st);
oos.flush();
oos.close();
}
}
-
- AC ED: STREAM_MAGIC. 聲明使用了序列化協議.
- 00 05: STREAM_VERSION. 序列化協議版本.
- 0x73: TC_OBJECT. 聲明這是一個新的對象.
- 0x72: TC_CLASSDESC. 聲明這裡開始一個新Class。
- 00 0A: Class名字的長度.
- 53 65 72 69 61 6c 54 65 73 74: SerialTest,Class類名.
- 05 52 81 5A AC 66 02 F6: SerialVersionUID, 序列化ID,如果沒有指定,
則會由演算法隨機產生一個8byte的ID.
- 0x02: 標記號. 該值聲明該對象支援序列化。
- 00 02: 該類所包含的域個數。
- 0x49: 域類型. 49 代表"I", 也就是Int.
- 00 07: 網域名稱字的長度.
- 76 65 72 73 69 6F 6E: version,網域名稱字描述.
- 0x4C: 域的類型.
- 00 03: 網域名稱字長度.
- 63 6F 6E: 網域名稱字描述,con
- 0x74: TC_STRING. 代表一個new String.用String來引用對象。
- 00 09: 該String長度.
- 4C 63 6F 6E 74 61 69 6E 3B: Lcontain;, JVM的標準對象簽名標記法.
- 0x78: TC_ENDBLOCKDATA,對象資料區塊結束的標誌
- 0x72: TC_CLASSDESC. 聲明這個是個新類.
- 00 06: 類名長度.
- 70 61 72 65 6E 74: parent,類名描述。
- 0E DB D2 BD 85 EE 63 7A: SerialVersionUID, 序列化ID.
- 0x02: 標記號. 該值聲明該對象支援序列化.
- 00 01: 類中域的個數.
- 0x49: 域類型. 49 代表"I", 也就是Int.
- 00 0D: 網域名稱字長度.
- 70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion,網域名稱字描述。
- 0x78: TC_ENDBLOCKDATA,對象塊結束的標誌。
- 0x70: TC_NULL, 說明沒有其他超類的標誌。.
- 00 00 00 0A: 10, parentVersion域的值.
- 00 00 00 42: 66, version域的值.
- 0x73: TC_OBJECT, 聲明這是一個新的對象.
- 0x72: TC_CLASSDESC聲明這裡開始一個新Class.
- 00 07: 類名的長度.
- 63 6F 6E 74 61 69 6E: contain,類名描述.
- FC BB E6 0E FB CB 60 C7: SerialVersionUID, 序列化ID.
- 0x02: Various flags. 標記號. 該值聲明該對象支援序列化
- 00 01: 類內的域個數。
- 0x49: 域類型. 49 代表"I", 也就是Int..
- 00 0E: 網域名稱字長度.
- 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, 網域名稱字描述.
- 0x78: TC_ENDBLOCKDATA對象塊結束的標誌.
- 0x70:TC_NULL,沒有超類了。
- 00 00 00 0B: 11, containVersion的值.
這個例子是相當的直白啦。SerialTest類實現了Parent超類,內部還持有一個Container對象。
序列化後的格式如下:
AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07
76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09
4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72
65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00
0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70
00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74
61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00
0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78
70 00 00 00 0B
我們來仔細看看這些位元組都代表了啥。開頭部分,見顏色:
序列化演算法的第一步就是輸出對象相關類的描述。例子所示對象為SerialTest類執行個體,
因此接下來輸出SerialTest類的描述。見顏色:
接下來,演算法輸出其中的一個域,int version=66;見顏色:
然後,演算法輸出下一個域,contain con = new contain();這個有點特殊,是個對象。
描述物件類型引用時需要使用JVM的標準對象簽名標記法,見顏色:
.接下來演算法就會輸出超類也就是Parent類描述了,見顏色:
下一步,輸出parent類的域描述,int parentVersion=100;同見顏色:
到此為止,演算法已經對所有的類的描述都做了輸出。下一步就是把執行個體對象的實際值輸出了。這時候是從parent Class的域開始的,見顏色:
還有SerialTest類的域:
再往後的bytes比較有意思,演算法需要描述contain類的資訊,要記住,
現在還沒有對contain類進行過描述,見顏色:
.輸出contain的唯一的域描述,int containVersion=11;
這時,序列化演算法會檢查contain是否有超類,如果有的話會接著輸出。
最後,將contain類實際域值輸出。
OK,我們討論了java序列化的機制和原理,希望能對同學們有所協助。
轉自 http://www.java3z.com/cwbwebhome/article/article8/862.html
serialVersionUID值的重要作用
根據上面的分析,可以發現如果一個類可序列化,serialVersionUID建議給一個確定的值,不要由系統自動產生,否則在增減欄位(不能修改欄位類型及長度)時,如果兩邊的類的版本不同會導致還原序列化失敗.
注意問題如果序列化時代碼這樣寫:
SerialTest st = new SerialTest();
oos.writeObject((parent)st);
會發現序列化的對象依然是SerialTest,如果在分布式環境中用Parent還原序列化(調用段不存在SerialTest),會造成ClassNotFoundException.
Java序列化機制和原理