effectiveJava

Effective Java - 深入解析序列化byte stream

這篇文章是閱讀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這個文件

Alt text

現在看他是天書 五分鐘後這個byte stream在你眼前就會變得非常赤裸

Are you Ready?

庖丁解牛

Alt text

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

Alt text

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

有興趣可以參考這裡

Alt text 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的值

Alt text

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