歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java序列化機制的深入研究

Java序列化機制的深入研究

日期:2017/3/1 10:14:16   编辑:Linux編程

1、java序列化簡介

序列化就是指對象通過寫出描述自己狀態的數值來記錄自己的過程,即將對象表示成一系列有序字節,java提供了將對象寫入流和從流中恢復對象的方法。對象能包含其它的對象,而其它的對象又可以包含另外的對象。JAVA序列化能夠自動的處理嵌套的對象。對於一個對象的簡單域,writeObject()直接將其值寫入流中。當遇到一個對象域時,writeObject()被再次調用,如果這個對象內嵌另一個對象,那麼,writeObject() 又被調用,直到對象能被直接寫入流為止。程序員所需要做的是將對象傳入ObjectOutputStream 的writeObject() 方法,剩下的將有系統自動完成。

要實現序列化的類必須實現的java.io.Serializable或java.io. Externalizable接口,否則將產生一個NotSerializableException。該接口內部並沒有任何方法,它只是一個"tagging interface" ,僅僅"tags"它自己的對象是一個特殊的類型。類通過實現 java.io.Serializable 接口以啟用其序列化功能。未實現此接口的類將無法使其任何狀態序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用於標識可序列化的語義。Java的"對象序列化"能讓你將一個實現了Serializable接口的對象轉換成一組byte,這樣日後要用這個對象時候,你就能把這些byte數據恢復出來,並據此重新構建那個對象了。

序列化圖示

反序列化圖示

在序列化的時候,writeObject與readObject之間是有先後順序的。readObject將最先write的object read出來。用數據結構的術語來講就稱之為先進先出!

2、序列化的必要性及目的

Java中,一切都是對象,在分布式環境中經常需要將Object從這一端網絡或設備傳遞到另一端。這就需要有一種可以在兩端傳輸數據的協議。Java序列化機制就是為了解決這個問題而產生。

Java序列化支持的兩種主要特性:

l Java 的RMI使本來存在於其他機器的對象可以表現出就象本地機器上的行為。

l 將消息發給遠程對象時,需要通過對象序列化來傳輸參數和返回值.

Java序列化的目的:

l 支持運行在不同虛擬機上不同版本類之間的雙向通訊;

l 定義允許JAVA類讀取用相同類較老版本寫入的數據流的機制;

l 定義允許JAVA類寫用相同類較老版本讀取的數據流的機制;

l 提供對持久性和RMI的序列化;

l 產生壓縮流且運行良好以使RMI能序列化;

l 辨別寫入的是否是本地流;

l 保持非版本化類的低負載;

3、序列化異常

序列化對象期間可能拋出6種異常:

l InvalidClassException 通常在重序列化流無法確定類型時或返回的類無法在取得對象的系統中表示時拋出此異常。異常也在恢復的類不聲明為public時或沒有public缺省(無變元)構造器時拋出。

l NotSerializableException 通常由具體化對象(負責自身的重序列化)探測到輸入流錯誤時拋出。錯誤通常由意外不變量值指示,或者表示要序列化的對象不可序列化。

l StreamCorruptedException 在存放對象的頭或控制數據無效時拋出。

l OptionalDataException 流中應包含對象但實際只包含原型數據時拋出。

l ClassNotFoundException 流的讀取端找不到反序列化對象的類時拋出。

l IOException 要讀取或寫入的對象發生與流有關的錯誤時拋出。

4、序列化一個對象

序列化一個對象,以及對序列化後的對象進行操作,需要遵循以下3點:

1、 一個對象能夠序列化的前提是實現Serializable接口或Externalizable接口,Serializable接口沒有方法,更像是個標記。有了這個標記的Class就能被序列化機制處理。

2、 寫個程序將對象序列化並輸出。ObjectOutputStream能把Object輸出成Byte流。

3、 要從持久的文件中讀取Bytes重建對象,我們可以使用ObjectInputStream。

在序列化時,有幾點要注意的:

l 當一個對象被序列化時,只序列化對象的非靜態成員變量,不能序列化任何成員方法和靜態成員變量。

l 如果一個對象的成員變量是一個對象,那麼這個對象的數據成員也會被保存。

l 如果一個可序列化的對象包含對某個不可序列化的對象的引用,那麼整個序列化操作將會失敗,並且會拋出一個NotSerializableException。可以通過將這個引用標記為transient,那麼對象仍然可以序列化。對於一些比較敏感的不想序列化的數據,也可以采用該標識進行修飾。

5、對象的序列化格式

5.1 簡單對象的序列化介紹

package com.asc.alibaba.base;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectOutputStream;

import java.io.Serializable;

publicclassTestSerial implements Serializable{

publicbyteversion = 100;

publicbytecount = 0;

publicstaticvoid main(String[] args) throws IOException, ClassNotFoundException {

FileOutputStream fos = new FileOutputStream("temp.out");

ObjectOutputStream oos = new ObjectOutputStream(fos);

TestSerialize testSerialize = new TestSerialize();

oos.writeObject(testSerialize);

oos.flush();

oos.close();

}

}

}

將一個對象序列化後是什麼樣子呢?打開剛才將對象序列化輸出的temp.out文件,以16進制方式顯示。內容應該如下:

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05
63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78
70 00 64
這一堆字節就是用來描述序列化以後的TestSerial對象的,我們注意到TestSerial類中只有兩個域:

public byte version = 100;

public byte count = 0;

且都是byte型,理論上存儲這兩個域只需要2個byte,但是實際上temp.out占據空間為51bytes,也就是說除了數據以外,還包括了對序列化對象的其他描述。

開頭部分,見顏色:

² AC ED: STREAM_MAGIC. 聲明使用了序列化協議.

² 00 05: STREAM_VERSION. 序列化協議版本.

² 0x73: TC_OBJECT. 聲明這是一個新的對象.

輸出TestSerial類的描述。見顏色:

² 0x72: TC_CLASSDESC. 聲明這裡開始一個新Class。

² 00 0A: Class名字的長度.

² 53 65 72 69 61 6c 54 65 73 74: TestSerial,Class類名.

² 05 52 81 5A AC 66 02 F6: SerialVersionUID, 序列化ID,如果沒有指定,
則會由算法隨機生成一個8byte的ID.

² 0x02: 標記號. 該值聲明該對象支持序列化。

² 00 02: 該類所包含的域個數。

輸出域的信息,見顏色:

² 0x42: 域類型. 42 代表"B", 也就是byte;

² 00 05: 域名字的長度;

² 636F 75 6E 74: count,域名字描述count;

² 0x42: 域類型. 42 代表"B", 也就是byte;

² 00 07: 域名字的長度;

²  76 65 72 73 69 6F 6E 78 70: version,域名字描述version;
塊的結束標記:見顏色:
0x78: TC_ENDBLOCKDATA,對象塊結束的標志。

0x70:TC_NULL,沒有超類了。

輸出域的值信息,見顏色:
²  00: 域值為00;
²  64: 域值為100;

5.2 復雜對象的序列化介紹

package com.asc.alibaba.base;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectOutputStream;

import java.io.Serializable;

class Contain implements Serializable {

publicintcontainVersion = 11;

}

class Parent implements Serializable {

publicintparentVersion = 10;

}

publicclass SerialTest extends Parent implements Serializable {

publicint version = 66;

public Contain con = new Contain();

publicint getVersion() {

returnversion;

}

publicstaticvoid main(String[] args) throws IOException {

FileOutputStream fos = new FileOutputStream("temp1.out");

ObjectOutputStream oos = new ObjectOutputStream(fos);

SerialTest testSerialize = new SerialTest();

oos.writeObject(testSerialize);

oos.flush();

oos.close();

FileInputStream fis = new FileInputStream("temp1.out");

byte[] bb = newbyte[200];

while (fis.read(bb) != -1) {

for (byte b : bb) {

System.out.print(Integer.toHexString(b));

System.out.print(" ");

}

}

}

}

這個例子中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

開頭部分,見顏色:

² ACED: STREAM_MAGIC. 聲明使用了序列化協議;

² 0005: STREAM_VERSION. 序列化協議版本;

² 0x73: TC_OBJECT. 聲明這是一個新的對象;

輸出TestSerial類的描述。見顏色:

² 0x72: TC_CLASSDESC. 聲明這裡開始一個新Class;

² 000A: Class名字的長度;

² 5365 72 69 61 6c 54 65 73 74: SerialTest,Class類名;

² 0552 81 5A AC 66 02 F6: SerialVersionUID, 序列化ID,如果沒有指定,則會由算法隨機生成一個8byte的ID;

² 0x02: 標記號. 該值聲明該對象支持序列化;

² 0002: 該類所包含的域個數;

輸出域的信息,見顏色:

² 0x49: 域類型. 49 代表"I", 也就是Int.

² 00 07: 域名字的長度.

² 76 65 72 73 69 6F 6E: version,域名字描述.

算法輸出下一個域,contain con = new contain();這個有點特殊,是個對象。描述對象類型引用時需要使用JVM的標准對象簽名表示法,見顏色:

² 0x4C: 域的類型;

² 0003: 域名字長度;

² 636F 6E: 域名字描述,con;

² 0x74: TC_STRING. 代表一個new String.用String來引用對象;

² 0009: 該String長度;

² 4C63 6F 6E 74 61 69 6E 3B: Lcontain;,JVM的標准對象簽名表示法;

² 0x78: TC_ENDBLOCKDATA,對象數據塊結束的標志;

算法就會輸出超類也就是Parent類描述了,見顏色:

² 0x72: TC_CLASSDESC. 聲明這個是個新類;

² 00 06: 類名長度;

² 70 6172 65 6E 74: parent,類名描述;

² 0E DBD2 BD 85 EE 63 7A: SerialVersionUID, 序列化ID;

² 0x02: 標記號. 該值聲明該對象支持序列化;

² 00 01: 類中域的個數;

輸出parent類的域描述,int parentVersion=100;見顏色:

² 0x49: 域類型. 49 代表"I", 也就是Int;

² 00 0D: 域名字長度;

² 70 6172 65 6E 74 56 65 72 73 69 6F 6E: parentVersion,域名字描述;

² 0x78: TC_ENDBLOCKDATA,對象塊結束的標志;

² 0x70: TC_NULL, 說明沒有其他超類的標志;

到此為止,算法已經對所有的類的描述都做了輸出。下一步就是把實例對象的實際值輸出了。這時候是從parent Class的域開始的,見顏色:

² 00 00 00 0A: 10, parentVersion域的值.

² 還有SerialTest類的域:

² 00 00 00 42: 66, version域的值.

再往後的bytes比較有意思,算法需要描述contain類的信息,要記住,
現在還沒有對contain類進行過描述,見顏色:

² 0x73: TC_OBJECT, 聲明這是一個新的對象;

² 0x72: TC_CLASSDESC聲明這裡開始一個新Class;

² 0007: 類名的長度;

² 636F 6E 74 61 69 6E:contain,類名描述;

² FCBB E6 0E FB CB 60 C7: SerialVersionUID, 序列化ID;

² 0x02: Various flags. 標記號. 該值聲明該對象支持序列化;

² 0001: 類內的域個數;

輸出contain的唯一的域描述,int containVersion=11:

² 0x49: 域類型. 49 代表"I", 也就是Int;

² 000E: 域名字長度;

² 636F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, 域名字描述;

² 0x78: TC_ENDBLOCKDATA對象塊結束的標志;

這時,序列化算法會檢查contain是否有超類,如果有的話會接著輸出;

² 0x70:TC_NULL,沒有超類了;

最後,將contain類實際域值輸出:

² 00 00 00 0B: 11, containVersion的值。

Copyright © Linux教程網 All Rights Reserved