Java序列化就是將一個對象轉化為一個二進制表示的字節數組,通過保存或則轉移這些二進制數組達到持久化的目的。要實現序列化,需要實現java.io.Serializable接口。反序列化是和序列化相反的過程,就是把二進制數組轉化為對象的過程。在反序列化的時候,必須有原始類的模板才能將對象還原。從這個過程我們可以猜測到,序列化過程並不想class文件那樣保存類的完整的結構信息。下面我們以一個簡單的例子來看一下,序列化的時候都保存了哪些信息。代碼如下:
package com.ysl;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializableTest implements Serializable{
private static final long serialVersionUID = -1L;
public int num = 2018;
public static void main(String[] args){
try {
FileOutputStream fos = new FileOutputStream("serializable");
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerializableTest test = new SerializableTest();
oos.writeObject(test);
oos.flush();
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
序列化后的二進制字節數據如下:
aced 0005 7372 0018 636f 6d2e 7973 6c2e
5365 7269 616c 697a 6162 6c65 5465 7374
ffff ffff ffff ffff 0200 0149 0003 6e75
6d78 7000 0007 e2
上述的內容分為一下幾個部分:
第一部分是序列化文件頭
- AC ED :STREAM_MAGIC聲明使用了序列化協議
- 00 05 :STREAM_VERSION序列化協議版本
- 73 :TC_OBJECT聲明這是一個新的對象
第二部分是序列化的類的描述,在這里是SerializableTest
- 72 :TC_CLASSDESC聲明這里開始一個新的class
- 00 18:class名字的長度是24個字節
- 636f 6d2e 7973 6c2e 5365 7269 616c 697a 6162 6c65 5465 7374:SerializableTest的完整類名
- ffff ffff ffff ffff:serialVersionUID,序列化ID,如果沒有指定,則會由算法隨機生成一個8字節的ID
- 02 :標記號,聲明該類支持序列化
- 00 01:該類所包含的域的個數為1
第三部分是對象中各個屬性的描述
- 49:域類型,49代表I,也就是int類型
- 00 03:域名字的長度為3
- 6e 75 6d:num屬性的名稱
第四部分為對象的父類信息描述
SerializableTest沒有父類,如果有,和第二部分的描述相同
- 78 :TC_ENDBLOCKDATA,對象塊的結束標志
- 70:TC_NUL:說明沒有其他超類的標志
第五部分為對象屬性的實際值
如果屬性是一個對象,那么這里還將序列化這個對象,規則和第二部分一樣
- 00 0007 e2:數值2018
雖然Java的序列化能夠保證對象狀態的持久保存,但是遇到一些對象結構復雜的情況還是比較難處理的,下面是對一些復雜情況的總結:
- 當父類實現了Serializable接口的時候,所有的子類都能序列化
- 子類實現了Serializable接口,父類沒有,父類中的屬性不能被序列化(不報錯,但是數據會丟失)
- 如果序列化的屬性是對象,對象必須也能序列化,否則會報錯
- 反序列化的時候,如果對象的屬性有修改或則刪減,修改的部分屬性會丟失,但是不會報錯
- 在反序列化的時候serialVersionUID被修改的話,會反序列化失敗
- 在存Java環境下使用Java的序列化機制會支持的很好,但是在多語言環境下需要考慮別的序列化機制,比如xml,json,或則protobuf