Serialization 是把對(duì)象的狀態(tài)轉(zhuǎn)換為字節(jié)流衷恭,同時(shí)字節(jié)流也可以轉(zhuǎn)換為對(duì)象,反向過程叫做 Deserialization
串行化可以把對(duì)象的狀態(tài)保存到文件中鸳慈,也可以通過網(wǎng)絡(luò)傳輸對(duì)象
串行化接口
java.io.Serializable
接口是一個(gè)標(biāo)記接口(不含有數(shù)據(jù)和方法)拂募,String
和所有的原始數(shù)據(jù)類型的包裝器類都默認(rèn)實(shí)現(xiàn)了該接口
ObjectOutputStream
類用來串行化對(duì)象為OutputStream
,類字段如果是引用帅刊,對(duì)應(yīng)的引用對(duì)象也需要序列化
ObjectInputStream
類用來反序列化先前串行化的原始數(shù)據(jù)和對(duì)象掂骏,重構(gòu)對(duì)象
可以寫入多個(gè)對(duì)象或原始數(shù)據(jù)類型到輸出流,這些對(duì)象必須從相應(yīng)的ObjectInputstream
讀取厚掷,類型和順序應(yīng)該要和寫入的相同
FileOutputStream fos = new FileOutputStream("t.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeInt(12345);
oos.writeObject("Today");
oos.writeObject(new Date());
oos.close();
FileInputStream fis = new FileInputStream("t.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);
int i = ois.readInt();
String today = (String) ois.readObject();
Date date = (Date) ois.readObject();
ois.close();
- 可以使用try/catch弟灼,處理轉(zhuǎn)換過程中可能出現(xiàn)的異常
- 使用
transient
修飾符的字段,不會(huì)被序列化 - 靜態(tài)字段不會(huì)序列化(serialVersionUID例外)
- 不是每個(gè)類都可序列化冒黑,有些類是不能序列化的田绑, 例如涉及線程的類
- 子類實(shí)現(xiàn)Serializable接口而父類未實(shí)現(xiàn)時(shí),父類不會(huì)被序列化
- 父類實(shí)現(xiàn)序列化抡爹,子類自動(dòng)實(shí)現(xiàn)序列化
如果需要特殊處理序列化和反序列化掩驱,可以在類中自定義序列化方法
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
為了避免因?yàn)椋琂AVA的序列化機(jī)制采用了一種特殊的算法:
1冬竟、所有保存到磁盤中的對(duì)象都有一個(gè)序列化編號(hào)
2欧穴、當(dāng)程序試圖序列化一個(gè)對(duì)象時(shí),會(huì)先檢查該對(duì)象是否已經(jīng)被序列化過泵殴,只有該對(duì)象從未(在本次虛擬機(jī)中)被序列化涮帘,系統(tǒng)才會(huì)將該對(duì)象轉(zhuǎn)換成字節(jié)序列并輸出
3、如果對(duì)象已經(jīng)被序列化笑诅,程序?qū)⒅苯虞敵鲆粋€(gè)序列化編號(hào)调缨,而不是重新序列化
serialVersionUID
用來確保在反序列化的過程中疮鲫,加載的是同樣的類(序列號(hào)對(duì)應(yīng)的類)
語法:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 1L;
原因:
有可能序列化一個(gè)對(duì)象到文件中,幾個(gè)月后才在不同的JVM進(jìn)行反序列化弦叶,此時(shí)對(duì)應(yīng)的類可能已經(jīng)改變了俊犯。
如果要反序列化的serialVersionUID
不相同,產(chǎn)生異常InvalidClassException
伤哺。
生成方式:
- 顯式聲明燕侠,比如和系統(tǒng)的版本保持一致
- 自動(dòng)生成,沒有顯式聲明的時(shí)候立莉,進(jìn)行序列化的時(shí)候根據(jù)相關(guān)規(guī)則產(chǎn)生一個(gè)默認(rèn)的
serialVersionUID
贬循,但是相應(yīng)的計(jì)算對(duì)類的細(xì)節(jié)非常敏感,可能編譯器的實(shí)現(xiàn)而有所不同桃序,所以強(qiáng)烈建議所有可序列化的類都明確聲明serialVersionUID
值
Externalizable
是Serializable
接口的子類杖虾,通過特定的兩個(gè)方法來指定要序列化的對(duì)象,而父類直接序列化所有對(duì)象
writeExternal(ObjectOutput out)
readExternal(ObjectInput in)
與父類
Serializable
的區(qū)別媒熊,反序列化重構(gòu)對(duì)象時(shí)奇适,先通過一個(gè)public的無參數(shù)構(gòu)造函數(shù)創(chuàng)建對(duì)象,再調(diào)用readExternal
方法芦鳍,父類是直接通過ObjectInputStream
創(chuàng)建的
測試
import java.io.*;
public class Solution {
public static void main(String[] args) throws IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("/home/wdy/Desktop/test"));
outputStream.writeObject(new Test(0xBBBBBBBB,"Wang"));
outputStream.close();
}
public static class Test implements Serializable {
public static final long serialVersionUID = 0xAAAAAAAAAAAAAAAAL;
int num;
String name;
public Test(int num, String name) {
this.num = num;
this.name = name;
}
}
}
寫出的二進(jìn)制數(shù)據(jù):
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: AC ED 00 05 73 72 00 21 63 6F 6D 2E 67 69 74 68 ,m..sr.!com.gith
00000010: 75 62 2E 77 61 6E 67 64 79 31 32 2E 53 6F 6C 75 ub.wangdy12.Solu
00000020: 74 69 6F 6E 24 54 65 73 74 AA AA AA AA AA AA AA tion$Test*******
00000030: AA 02 00 02 49 00 03 6E 75 6D 4C 00 04 6E 61 6D *...I..numL..nam
00000040: 65 74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 et..Ljava/lang/S
00000050: 74 72 69 6E 67 3B 78 70 BB BB BB BB 74 00 04 57 tring;xp;;;;t..W
00000060: 61 6E 67 ang
解釋:
序列化會(huì)記錄每個(gè)類的名稱嚷往,字段的名稱和類型,最后才是具體的數(shù)據(jù)類型
ObjectOutputStream
初始化時(shí)就會(huì)寫出頭信息:
- 寫出
AC ED
表示魔數(shù)柠衅,即文件類型皮仁,寫出版本號(hào)00 05
,這兩個(gè)字段都是固定的
寫一個(gè)普通的對(duì)象writeOrdinaryObject
- 對(duì)象標(biāo)志
0x73
類描述信息writeClassDesc
- 類標(biāo)志
0x72
菲宴,表示一個(gè)新的類描述 - 類名稱(
modified-utf-8
格式贷祈,這里編碼長度為33字節(jié)即0x21
,大字節(jié)序?qū)懗鰹閟hort形式喝峦,之后是UTF內(nèi)容势誊,這里全部是單字節(jié)編碼com.github.wangdy12.Solution$Test
),序列號(hào)(8個(gè)0xAA
) - 標(biāo)志谣蠢,一個(gè)字節(jié)粟耻,表示不同的序列化類型(例如對(duì)象是否自定義了
writeObject
方法),這里為0x02
- 字段數(shù)目眉踱,兩個(gè)字節(jié)挤忙,這里為
00 02
,即兩個(gè)字段谈喳,接下來處理每個(gè)字段的描述信息 - int字段的寫出册烈,類型占用一個(gè)字節(jié),對(duì)應(yīng)為
I
叁执,即49
茄厘,接下來是字段名稱,前兩個(gè)字節(jié)表示長度00 03
谈宛,后面是具體的名稱num
- String字段的寫出:簽名第一個(gè)字符對(duì)應(yīng)為
L
次哈,即0x4C
,然后是字段名稱吆录,前兩個(gè)字節(jié)表示長度00 04
窑滞,后面是字段名稱name
,如果不是原始類型恢筝,再寫出其類型簽名哀卫,這里是Ljava/lang/String;
,以writeString
寫出其類型簽名撬槽,先寫標(biāo)志0x74
此改,然后內(nèi)部再調(diào)用writeUTF
以UTF形式寫出,長度0x12
侄柔,即18共啃,內(nèi)容Ljava/lang/String;
- 對(duì)象塊的結(jié)束標(biāo)志
0x78
- 遞歸寫出對(duì)象的父類信息描述,遞歸調(diào)用
writeClassDesc
暂题,這里為null移剪,寫出0x70
寫出具體的數(shù)據(jù)writeSerialData
- 如果有自定義的
writeObject
就調(diào)用,如果沒有使用默認(rèn)的defaultWriteFields
寫出薪者,先寫出原始數(shù)據(jù)類型的值纵苛,然后寫出對(duì)象字段中的實(shí)際數(shù)據(jù)
序列化使用的常量位于ObjectStreamConstants
類中,內(nèi)部包含一些標(biāo)志位
static final short STREAM_MAGIC = (short)0xaced;
static final short STREAM_VERSION = 5;
static final byte TC_NULL = (byte)0x70;
static final byte TC_CLASSDESC = (byte)0x72;
static final byte TC_OBJECT = (byte)0x73;
static final byte TC_STRING = (byte)0x74;
static final byte TC_ENDBLOCKDATA = (byte)0x78;
Kryo
一種更高效的的序列化方式言津,相同對(duì)象的序列化攻人,大小大大減小
public class Solution {
public static void main(String[] args) throws IOException {
Kryo kryo = new Kryo();
kryo.register(Test.class);//需要進(jìn)行注冊(cè),不注冊(cè)時(shí)改為 kryo.setRegistrationRequired(false);
Test test = new Test(0xBBBBBBBB,"Wang");
Output output = new Output(new FileOutputStream("/home/wdy/Desktop/test-kryo"));
kryo.writeClassAndObject(output, test);
output.close();
Input input = new Input(new FileInputStream("/home/wdy/Desktop/test-kryo"));
Object object2 = kryo.readClassAndObject(input);
input.close();
System.out.println(((Test)object2).num);
}
}
注冊(cè)后序列化結(jié)果只有10個(gè)字節(jié)悬槽,不包含類型信息贝椿,第一個(gè)字節(jié)是一個(gè)變長int,表示注冊(cè)對(duì)應(yīng)的序號(hào)陷谱,之后四個(gè)字節(jié)表示Wang
烙博,且g
最后一個(gè)字節(jié)的最高位為1,即最后一個(gè)字節(jié)為負(fù)數(shù)烟逊,表示字符串結(jié)束渣窜,最后五個(gè)字節(jié)是一個(gè)邊長編碼的int,即0xBBBBBBBB
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 0B 57 61 6E E7 89 91 A2 C4 08 .Wang.."D.
如果不進(jìn)行注冊(cè)宪躯,對(duì)應(yīng)結(jié)果會(huì)記錄類名稱
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 01 00 63 6F 6D 2E 67 69 74 68 75 62 2E 77 61 6E ..com.github.wan
00000010: 67 64 79 31 32 2E 53 6F 6C 75 74 69 6F 6E 24 54 gdy12.Solution$T
00000020: 65 73 F4 57 61 6E E7 89 91 A2 C4 08 estWang.."D.