Java序列化算法透析

jopen 10年前發布 | 22K 次閱讀 Java 算法

Java 序列化算法透析

Serialization (序列化)是一種將對象以一連串的字節描述的過程;反序列化 deserialization 是一種將這些字節重建成一個對象的過程。 Java 序列化 API 提供一種處理對象序列化的標準機制。在這里你能學到如何序列化一個對象,什么時候需要序列化以及 Java 序列化的算法,我們用一個實例來示范序列化以后的字節是如何描述一個對象的信息的。

序列化的必要性

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

如何序列化一個對象

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

import java.io.Serializable;

class TestSerial implements Serializable {

   public byte version = 100;

   public byte count = 0;

}</pre></div>

然后我們寫個程序將對象序列化并輸出。 ObjectOutputStream 能把 Object 輸出成 Byte 流。我們將 Byte 流暫時存儲到 temp.out 文件里。

public static void main(String args[]) throws IOException {

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

   ObjectOutputStream oos = new ObjectOutputStream(fos);

   TestSerial ts = new TestSerial();

   oos.writeObject(ts);

   oos.flush();

   oos.close();

}</pre>

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

public static void main(String args[]) throws IOException {

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

   ObjectInputStream oin = new ObjectInputStream(fis);

   TestSerial ts = (TestSerial) oin.readObject();

   System.out.println("version="+ts.version);

}</pre>

 執行結果為

</span>

100.

對象的序列化格式

將一個對象序列化后是什么樣子呢?打開剛才我們將對象序列化輸出的 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</pre>

這一坨字節就是用來描述序列化以后的

</span>

TestSerial 對象的,我們注意到 TestSerial 類中只有兩個域:

public byte version = 100;

public byte count = 0;

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

Java 的序列化算法

序列化算法一般會按步驟做如下事情:

l       將對象實例相關的類元數據輸出。

l       遞歸地輸出類的超類描述直到不再有超類。

l       類元數據完了以后,開始從最頂層的超類開始 輸出對象實例的實際數據值。

l       從上至下遞歸輸出實例的數據

我們用另一個更完整覆蓋所有可能出現的情況的例子來說明:

class parent implements Serializable {

   int parentVersion = 10;

}

class contain implements Serializable{

   int containVersion = 11;

}

public class SerialTest extends parent implements Serializable {

   int version = 66;

   contain con = new contain();



   public int getVersion() {

          return version;

   }

   public static void main(String args[]) throws IOException {

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

          ObjectOutputStream oos = new ObjectOutputStream(fos);

          SerialTest st = new SerialTest();

          oos.writeObject(st);

          oos.flush();

          oos.close();

   }

}</pre>

這個例子是相當的直白啦。 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

 

我們來仔細看看這些字節都代表了啥。開頭部分,見顏色

  • AC ED: STREAM_MAGIC. 聲明使用了序列化協議 .
    </li>

  • 00 05: STREAM_VERSION. 序列化協議版本 .
    </li>

  • 0x73: TC_OBJECT. 聲明這是一個新的對象 .    
    </li> </ul>

    序列化算法的第一步就是輸出對象相關類的描述。例子所示對象為 SerialTest 類實例,因此接下來輸出 SerialTest 類的描述。見顏色

    • 0x72: TC_CLASSDESC. 聲明這里開始一個新 Class
      </li>

    • 00 0A: Class 名字的長度 .
      </li>

    • 53 65 72 69 61 6c 54 65 73 74: SerialTest,Class 類名 .
      </li>

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

    • 0x02: 標記號 . 該值聲明該對象支持序列化。
      </li>

    • 00 02: 該類所包含的域個數。
      </li> </ul>

      接下來,算法輸出其中的一個域, int version=66 ;見顏色

      • 0x49: 域類型 . 49 代表 "I", 也就是 Int.
        </li>

      • 00 07: 域名字的長度 .
        </li>

      • 76 65 72 73 69 6F 6E: version, 域名字描述 .
        </li> </ul>

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

        • 0x4C: 域的類型 .
          </li>

        • 00 03: 域名字長度 .
          </li>

        • 63 6F 6E: 域名字描述, con
          </li>

        • 0x74: TC_STRING. 代表一個 new String. String 來引用對象。
          </li>

        • 00 09: String 長度 .
          </li>

        • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain ;, JVM 的標準對象簽名表示法 .
          </li>

        • 0x78: TC_ENDBLOCKDATA, 對象數據塊結束的標志
          </li> </ul>

          . 接下來算法就會輸出超類也就是 Parent 類描述了,見顏色

          • 0x72: TC_CLASSDESC. 聲明這個是個新類 .
            </li>

          • 00 06: 類名長度 .
            </li>

          • 70 61 72 65 6E 74: parent, 類名描述。
            </li>

          • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID , 序列化 ID.
            </li>

          • 0x02: 標記號 . 該值聲明該對象支持序列化 .
            </li>

          • 00 01: 類中域的個數 .
            </li> </ul>

            下一步,輸出 parent 類的域描述, int parentVersion =100; 同見顏色

            • 0x49: 域類型 . 49 代表 "I", 也就是 Int.
              </li>

            • 00 0D: 域名字長度 .
              </li>

            • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion ,域名字描述。
              </li>

            • 0x78: TC_ENDBLOCKDATA, 對象塊結束的標志。
              </li>

            • 0x70: TC_NULL, 說明沒有其他超類的標志。 .
              </li> </ul>

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

              • 00 00 00 0A: 10, parentVersion 域的值 .
                </li> </ul>

                還有 SerialTest 類的域:

                • 00 00 00 42: 66, version 域的值 .
                  </li> </ul>

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

                  • 0x73: TC_OBJECT, 聲明這是一個新的對象 .
                    </li>

                  • 0x72: TC_CLASSDESC 聲明這里開始一個新 Class.
                    </li>

                  • 00 07: 類名的長度 .
                    </li>

                  • 63 6F 6E 74 61 69 6E: contain, 類名描述 .
                    </li>

                  • FC BB E6 0E FB CB 60 C7: SerialVersionUID , 序列化 ID.
                    </li>

                  • 0x02: Various flags. 標記號 . 該值聲明該對象支持序列化
                    </li>

                  • 00 01: 類內的域個數。
                    </li> </ul>

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

                    • 0x49: 域類型 . 49 代表 "I", 也就是 Int..
                      </li>

                    • 00 0E: 域名字長度 .
                      </li>

                    • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion , 域名字描述 .
                      </li>

                    • 0x78: TC_ENDBLOCKDATA 對象塊結束的標志 .
                      </li> </ul>

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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!