Effective Java - 深入解析序列化byte stream
October 12, 2017這篇文章是閱讀Effective Java 第12章 - Item88之前需要會的知識
關於序列化的Protocol詳細文件在這 不過這篇門檻太高 需要知道什麼是Context Free Grammar 有興趣的各位自動機大神可以來看一下
深談序列化
序列化的目的是為了傳輸或儲存 所以最重要的事情就是序列化的那端用的protocol跟反序列化的那端用的protocol一樣
既然如此 Java就定義了所有人都要共同遵尋的protocol
1.寫下你當下的class的metadata
2.Recursively寫parent class的metadata 寫到java.lang.Object為止
3.再來 寫下class的instance variable的值 從最Parent class的instance開始寫
直上例子
序列化的目標當然是我們最可愛的Person Class
class Person implements Serializable {
public String name;
public int age;
Person() {
this("John",1);
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
跑以下的程式 就會把Person物件存進person.ser這個文件裡
public class TestDrive{
public static void main(String[] args) {
String filename = "person.ser";
Person p = new Person();
// save the object to file
FileOutputStream fos = null;
ObjectOutputStream out = null;
try {
fos = new FileOutputStream(filename);
out = new ObjectOutputStream(fos);
out.writeObject(p);
out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
我們來仔細看看person.ser這個文件
現在看他是天書 五分鐘後這個byte stream在你眼前就會變得非常赤裸
Are you Ready?
庖丁解牛
ACED: MAGIC NUMBER 告訴你說這是段被序列化的byte stream
0005: STREAM_VERSION STREAM的版本
73: 新的Object
72 新的Class descriptor 接下來的是一個新的class
0006 Class name長度 (Person: 6)
506572736F6E ClassName 把16進位的值轉成ASCII
0x50 = 80 = P
0x65 = 101 = e
0x72 = 114 = r
0x73 = 115 = s
0x6F = 111 = o
0x6E = 110 = n
B983994E4F36A9BC serialVersionUID 如果你在你的Person寫死serialVersionUID 那個值就會出現在這裡
02 表示這個object支援序列化
殺進物件內部 繼續描述這個class
0002: 這個物件有多少個instance (name, age 兩個)
49: 第一個variable型態 0x49 = “I” 代表integer
0003: 這個variable長度 3
616765
0x61 = 97 = a
0x67 = 103 = g
0x65 = 101 = e
4C: 這個variable型態 “L” 代表object
0004: 這個variable長度 4
6E616D65
0x6E = 110 = n
0x61 = 97 = a
0x6D = 109 = m
0x65 = 101 = e
74: 接下來是個String (TC_STRING = (byte)0x74;)
0012: 接下來這個String長度是18
4C6A6176612F6C616E672F537472696E673B:
“Ljava/lang/String;”
因為String並不是java的Primitive type 只能算是一個object
那在序列化如何描述一個物件呢 來看一下CFG:
objectDesc:
obj_typecode fieldName className1
原來在byte stream裡面要描述一個物件 需要先說object的type 再說object的name 再說class的name
至於為什麼前面有一個L? JVM會用最簡潔的方式儲存class L[class]; 代表一個class
有興趣可以參考這裡
78 結束對象標誌(TC_ENDBLOCKDATA = (byte)0x78)
這個class描述完了 如果這個class有parent class 那就會從72(點我)開始 recursive繼續描述class
70 Recursive完畢 因為Person沒有父類 所以寫完一個class description就結束(TC_NULL = (byte)0x70;)
看到70就知道 所有class都描述完了 再來 開始記錄object裡面的instance的值
00000001 第一個變數的value
74 接下來是個String (TC_STRING = (byte)0x74;)
0004: 接下來這個String長度是4
4A6F686E:
“John”
總結
因為這個序列化可能會很長 也會有很多class重複用到很多次 java不會每次看到同樣的class還跟第一次看到一樣全部寫上去 所以對於每一個已經寫過的物件或是已經寫過的class descriptor 它會用一個reference serial number記住他
下次再遇到一個一樣的class 我就直接寫那個出現過的class的reference number
下次再遇到一個一樣的object 我就直接寫那個以前寫好的那個object的reference number