1. 程式人生 > >JAVA序列化機制的深入研究

JAVA序列化機制的深入研究

1、java序列化簡介

序列化就是指物件通過寫出描述自己狀態的數值來記錄自己的過程,即將物件表示成一系列有序位元組,java提供了將物件寫入流和從流中恢復物件的方法。物件能包含其它的物件,而其它的物件又可以包含另外的物件。JAVA序列化能夠自動的處理巢狀的物件。對於一個物件的簡單域,writeObject()直接將其值寫入流中。當遇到一個物件域時,writeObject()被再次呼叫,如果這個物件內嵌另一個物件,那麼,writeObject()又被呼叫,直到物件能被直接寫入流為止。程式設計師所需要做的是將物件傳入ObjectOutputStreamwriteObject()方法,剩下的將有系統自動完成。

要實現序列化的類必須實現的java.io.Serializablejava.io.Externalizable介面,否則將產生一個NotSerializableException。該介面內部並沒有任何方法,它只是一個"tagging interface",僅僅"tags"它自己的物件是一個特殊的型別。類通過實現 java.io.Serializable介面以啟用其序列化功能。未實現此介面的類將無法使其任何狀態序列化或反序列化。可序列化類的所有子型別本身都是可序列化的。序列化介面沒有方法或欄位,僅用於標識可序列化的語義。Java"物件序列化"能讓你將一個實現了Serializable

介面的物件轉換成一組byte,這樣日後要用這個物件時候,你就能把這些byte資料恢復出來,並據此重新構建那個物件了。

序列化圖示


反序列化圖示


在序列化的時候,writeObjectreadObject之間是有先後順序的。readObject將最先writeobject read出來。用資料結構的術語來講就稱之為先進先出!

2、序列化的必要性及目的

Java中,一切都是物件,在分散式環境中經常需要將Object從這一端網路或裝置傳遞到另一端。這就需要有一種可以在兩端傳輸資料的協議。Java序列化機制就是為了解決這個問題而產生。

Java序列化支援的兩種主要特性:

  • Java RMI使本來存在於其他機器的物件可以表現出就象本地機器上的行為。
  • 將訊息發給遠端物件時,需要通過物件序列化來傳輸引數和返回值.

Java序列化的目的:

  • 支援執行在不同虛擬機器上不同版本類之間的雙向通訊;
  • 定義允許JAVA類讀取用相同類較老版本寫入的資料流的機制;
  • 定義允許JAVA類寫用相同類較老版本讀取的資料流的機制;
  • 提供對永續性和RMI的序列化;
  • 產生壓縮流且執行良好以使RMI能序列化;
  • 辨別寫入的是否是本地流;
  • 保持非版本化類的低負載;

3、序列化異常

序列化物件期間可能丟擲6種異常:

  • InvalidClassException 通常在重序列化流無法確定型別時或返回的類無法在取得物件的系統中表示時丟擲此異常。異常也在恢復的類不宣告為public時或沒有public預設(無變元)構造器時丟擲。
  • NotSerializableException 通常由具體化物件(負責自身的重序列化)探測到輸入流錯誤時丟擲。錯誤通常由意外不變數值指示,或者表示要序列化的物件不可序列化。
  • StreamCorruptedException 在存放物件的頭或控制資料無效時丟擲。
  • OptionalDataException 流中應包含物件但實際只包含原型資料時丟擲。
  • ClassNotFoundException 流的讀取端找不到反序列化物件的類時丟擲。
  • IOException  要讀取或寫入的物件發生與流有關的錯誤時丟擲。

4、序列化一個物件

序列化一個物件,以及對序列化後的物件進行操作,需要遵循以下3點:

1、 一個物件能夠序列化的前提是實現Serializable介面或Externalizable介面,Serializable介面沒有方法,更像是個標記。有了這個標記的Class就能被序列化機制處理。

2、 寫個程式將物件序列化並輸出。ObjectOutputStream能把Object輸出成Byte流。

3、 要從持久的檔案中讀取Bytes重建物件,我們可以使用ObjectInputStream。 

在序列化時,有幾點要注意的:

  •  當一個物件被序列化時,只序列化物件的非靜態成員變數,不能序列化任何成員方法和靜態成員變數。
  •  如果一個物件的成員變數是一個物件,那麼這個物件的資料成員也會被儲存。
  • 如果一個可序列化的物件包含對某個不可序列化的物件的引用,那麼整個序列化操作將會失敗,並且會丟擲一個NotSerializableException。可以通過將這個引用標記為transient,那麼物件仍然可以序列化。對於一些比較敏感的不想序列化的資料,也可以採用該標識進行修飾。

5、物件的序列化格式

5.1 簡單物件的序列化介紹

[java] view plaincopyprint?
  1. package com.asc.alibaba.base;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileOutputStream;  
  4. import java.io.IOException;  
  5. import java.io.ObjectOutputStream;  
  6. import java.io.Serializable;  
  7. publicclassTestSerialimplements Serializable{  
  8.     publicbyteversion = 100;  
  9.     publicbytecount = 0;  
  10.     publicstaticvoid main(String[] args)throws IOException, ClassNotFoundException {  
  11.         FileOutputStream fos = new FileOutputStream("temp.out");  
  12.         ObjectOutputStream oos =new ObjectOutputStream(fos);  
  13.         TestSerialize testSerialize =new TestSerialize();  
  14.         oos.writeObject(testSerialize);  
  15.         oos.flush();  
  16.         oos.close();  
  17.         }  
  18.     }  
  19. }  

將一個物件序列化後是什麼樣子呢?開啟剛才將物件序列化輸出的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
這一堆位元組就是用來描述序列化以後的TestSerial物件的,我們注意到TestSerial類中只有兩個域:

public byte version = 100;

public byte count = 0;

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

開頭部分,見顏色

²  AC ED: STREAM_MAGIC. 宣告使用了序列化協議.

²  00 05: STREAM_VERSION. 序列化協議版本.

²  0x73: TC_OBJECT. 宣告這是一個新的物件.  

輸出TestSerial類的描述。見顏色:

²  0x72: TC_CLASSDESC. 宣告這裡開始一個新Class。

²  00 0A: Class名字的長度.

²  53 65 72 69 61 6c 54 65 73 74: TestSerial,Class類名.

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

²  0x02: 標記號. 該值宣告該物件支援序列化。

²  00 02: 該類所包含的域個數。

輸出域的資訊,見顏色

²  0x42: 域型別. 42 代表"B", 也就是byte;

²  00 05: 域名字的長度;

²  636F 75 6E 74: count,域名字描述count;

²  0x42: 域型別. 42 代表"B", 也就是byte;

²  00 07: 域名字的長度;

²  76 65 72 73 69 6F 6E 78 70: version,域名字描述version;
塊的結束標記:見顏色
0x78: TC_ENDBLOCKDATA,物件塊結束的標誌。

0x70:TC_NULL,沒有超類了。

輸出域的值資訊,見顏色:
²  00: 域值為00;
²  64: 域值為100;

5.2 複雜物件的序列化介紹

[java] view plaincopyprint?
  1. package com.asc.alibaba.base;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileOutputStream;  
  4. import java.io.IOException;  
  5. import java.io.ObjectOutputStream;  
  6. import java.io.Serializable;  
  7. classContain implements Serializable {  
  8.     publicintcontainVersion = 11;  
  9. }  
  10. classParent implements Serializable {  
  11.     publicintparentVersion = 10;  
  12. }  
  13. publicclassSerialTest extends Parent implements Serializable {  
  14.     publicint      version   = 66;  
  15.     public Containcon = new Contain();  
  16.     publicint getVersion() {  
  17.         returnversion;  
  18.     }  
  19.     publicstaticvoid main(String[] args)throws IOException {  
  20.         FileOutputStream fos = new FileOutputStream("temp1.out");  
  21.         ObjectOutputStream oos =new ObjectOutputStream(fos);  
  22.         SerialTest testSerialize =new SerialTest();  
  23.         oos.writeObject(testSerialize);  
  24.         oos.flush();  
  25.         oos.close();  
  26.         FileInputStream fis = new FileInputStream("temp1.out");  
  27.         byte[] bb =newbyte[200];  
  28.         while (fis.read(bb) != -1) {  
  29.             for (byte b : bb) {               System.out.print(Integer.toHexString(b));  
  30.                 System.out.print(" ");  
  31.             }  
  32.         }  
  33.     }  
  34. }  

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 7000 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

開頭部分,見顏色

²  ACED: STREAM_MAGIC. 宣告使用了序列化協議;

²  0005: STREAM_VERSION. 序列化協議版本;

²  0x73: TC_OBJECT. 宣告這是一個新的物件;

輸出TestSerial類的描述。見顏色:

²  0x72: TC_CLASSDESC. 宣告這裡開始一個新Class;

²  000A: Class名字的長度;

²  5365 72 69 61 6c 54 65 73 74: SerialTest,Class類名;

²  0552 81 5A AC 66 02 F6: SerialVersionUID, 序列化ID,如果沒有指定,則會由演算法隨機生成一個8byte的ID;

²  0x02: 標記號. 該值宣告該物件支援序列化;

²  0002: 該類所包含的域個數;

輸出域的資訊,見顏色

²  0x49: 域型別. 49 代表"I", 也就是Int.

²  00 07: 域名字的長度.

²  76 65 72 73 69 6F 6E: version,域名字描述.

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

²  0x4C: 域的型別;

²  0003: 域名字長度;

²  636F 6E: 域名字描述,con;

²  0x74: TC_STRING. 代表一個new String.用String來引用物件;

²  0009: 該String長度;

²  4C63 6F 6E 74 61 69 6E 3B: Lcontain;,JVM的標準物件簽名表示法;

²  0x78: TC_ENDBLOCKDATA,物件資料塊結束的標誌;

演算法就會輸出超類也就是Parent類描述了,見顏色

²  0x72: TC_CLASSDESC. 宣告這個是個新類;

²  00 06: 類名長度;

²  70 6172 65 6E 74: parent,類名描述;

²  0E DBD2 BD 85 EE 63 7A: SerialVersionUID, 序列化ID;

²  0x02: 標記號. 該值宣告該物件支援序列化;

²  00 01: 類中域的個數;

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

²  0x49: 域型別. 49 代表"I", 也就是Int;

²  00 0D: 域名字長度;

²  70 6172 65 6E 74 56 65 72 73 69 6F 6E: parentVersion,域名字描述;

²  0x78: TC_ENDBLOCKDATA,物件塊結束的標誌;

²  0x70: TC_NULL, 說明沒有其他超類的標誌;

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

²  00 00 00 0A: 10, parentVersion域的值.

²  還有SerialTest類的域:

²  00 00 00 42: 66, version域的值.

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

²  0x73: TC_OBJECT, 宣告這是一個新的物件;

²  0x72: TC_CLASSDESC宣告這裡開始一個新Class;

²  0007: 類名的長度;

²  636F 6E 74 61 69 6E:contain,類名描述;

²  FCBB E6 0E FB CB 60 C7: SerialVersionUID, 序列化ID;

²  0x02: Various flags. 標記號. 該值宣告該物件支援序列化;

²  0001: 類內的域個數;

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

²  0x49: 域型別. 49 代表"I", 也就是Int;

²  000E: 域名字長度;

²  636F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, 域名字描述;

²  0x78: TC_ENDBLOCKDATA物件塊結束的標誌;

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

²  0x70:TC_NULL,沒有超類了;

最後,將contain類實際域值輸出:

²  00 00 00 0B: 11, containVersion的值。

6、Java的序列化演算法

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

◆ 將物件例項相關的類元資料輸出。

◆ 遞迴地輸出類的超類描述直到不再有超類。

◆ 類元資料完了以後,開始從最頂層的超類開始輸出物件例項的實際資料值。

◆ 從上至下遞迴輸出例項的資料

7、序列化例項介紹

7.1 定製資料格式的序列化

驗證怎樣用writeObjectreadObject方法編碼一個定製資料格式。當有大量永續性資料時,資料應該以簡潔、精簡的格式存放。此例子用一個矩形對稱陣列,只對其一半資料序列化,即只寫/讀一半資料再恢復成完整陣列。

[java] view plaincopyprint?
  1. package com.asc.alibaba.base;  
  2. import java.io.*;  
  3. 相關推薦

    JAVA序列機制深入研究

    1、java序列化簡介 序列化就是指物件通過寫出描述自己狀態的數值來記錄自己的過程,即將物件表示成一系列有序位元組,java提供了將物件寫入流和從流中恢復物件的方法。物件能包含其它的物件,而其它的物件又可以包含另外的物件。JAVA序列化能夠自動的處理巢狀的物

    java序列機制(簡單使用)

    轉載:孤傲蒼狼 https://www.cnblogs.com/xdp-gacl/p/3777987.html 詳細分析:http://www.importnew.com/24490.html 一、序列化和反序列化的概念   把物件轉換為位元組序列的過程稱為物件的序列化。   

    java序列機制和Serialize介面

    java序列化機制 Serialize 介面 java本身的序列化機制存在問題: 1.序列化資料結果比較大,傳輸效率低 2.不能跨語言對接 XML編碼格式的物件序列化機制成為主流 序列化機制: MessagePack Protocol Buffers Du

    Java序列機制和原理

    本文講解了Java序列化的機制和原理。從文中你可以瞭解如何序列化一個物件,什麼時候需要序列化以及Java序列化的演算法。 有關Java物件的序列化和反序列化也算是Java基礎的一部分,下面對Java序列化的機制和原理進行一些介紹。 Java序列化演算法透析   Seri

    java 序列機制

    1. 序列化概念: 序列化就是一種用來處理物件流的機制,所謂物件流也就是將物件的內容進行流化,將資料分解成位元組流,以便儲存在檔案中或在網路上傳輸。可以對流化後的物件進行讀寫操作,也可將流化後的物件傳輸於網路之間。序列化是為了解決在對物件流進行讀寫操作時所引發的問題。 序

    Java I/O系統學習系列五:Java序列機制

      在Java的世界裡,建立好物件之後,只要需要,物件是可以長駐記憶體,但是在程式終止時,所有物件還是會被銷燬。這其實很合理,但是即使合理也不一定能滿足所有場景,仍然存在著一些情況,需要能夠在程式不執行的情況下保持物件,所以序列化機制應運而生。 1. 為什麼要有序列化   簡單來說序列化的作用就是將記憶體中的

    深入JAVA序列序列

    轉換 zed 一個 源代碼 () bili 什麽 知識點 dom 前言 java序列化與反序列化應該是非常基本的知識點,但細想起來還是一頭霧水, 不知道序列化與反序列化到底底層是如何實現的,所以特意花了些時間整理這篇文章。 所以你如果還只是停留在使用和知道這麽一個知

    java物件序列機制

    1.定義 java物件序列化機制允許實現了Serializable/Externalizable介面的java物件永久性地儲存到計算機的磁碟中,或則允許java物件直接在網路中傳輸,擺脫平臺的限制。反序列化即使將IO流中的位元組序列轉化為java物件。 2.原理 3.使用 序列化: 1)一個實現了S

    Java序列機制

    1、java本身的序列化存在的問題 1、序列化資料結果比較大、傳輸效率比較低 2、不能跨語言對接 對於該問題出現的xml、json等方式成為了熱門技術,但是這種序列化技術還是存在佔用空間大、效能低等問題,也就出現了二進位制序列化框架MessagePack等。 2、序列化概念

    深入理解JAVA序列

    如果你只知道實現 Serializable 介面的物件,可以序列化為本地檔案。那你最好再閱讀該篇文章,文章對序列化進行了更深一步的討論,用實際的例子程式碼講述了序列化的高階認識,包括父類序列化的問題、靜態變數問題、transient 關鍵字的影響、序列化 ID 問題。在筆

    深入理解Java序列和反序列

    序列化是一種物件持久化的手段。普遍應用在網路傳輸、RMI等場景中。本文通過分析ArrayList的序列化來介紹Java序列化的相關內容。主要涉及到以下幾個問題: 怎麼實現Java的序列化 為什麼實現了java.io.Serializable接口才能被序列化 trans

    java把物件轉化成流,和把流轉化成物件(包含clone機制+序列機制

    類如下,這裡為了測試 僅僅用了內部類class Book implements Serializable { int i = 1; }物件轉化成位元組Book b = new Book(); B

    Java反射機制深入研究

    Java反射是Java語言的一個很重要的特徵,它使得Java具體了“動態性”。 在Java執行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個物件,能否呼叫它的任意一個方法?答案是肯定的。這種動態獲取類的資訊以及動態呼叫物件的方法的功能來自於Java語言的反射(Reflection

    Java 物件序列機制詳解

    物件序列化的目標:將物件儲存到磁碟中,或允許在網路中直接傳輸物件。 物件序列化機制允許把記憶體中的Java物件轉換成平臺無關的二進位制流,從而允許把這種二進位制流持久的儲存在磁碟上,通過網路將這種二進位制流傳輸到另一個網路節點。其他程式一旦獲得了這種二進位制流,都可以講這種

    Java序列與ProtocalBuffer序列深入分析(轉)

    今天看了《Java序列化與ProtocalBuffer序列化之深入分析》,感覺有所收穫。原文中對ObjectStreamField中關於屬性型別與字元表示的對映沒有指出來,在原帖中回覆了作者,這裡稍作修改並轉發。 從一個簡單物件的序列化內容來看java序列化與ProtocalBuffer序列化機制的

    原型模式深入--使用序列機制實現物件的深克隆

    其實物件的深克隆還可以通過序列化來實現,直接上程式碼,物件使用的還是之前的sheep物件,但是要實現Serializable的介面: public class Client3 { publ

    深入學習Java序列

    類定義 all sstream tno object protocol text ans contain 前言 對於Java的序列化,一直只知道只需要實現Serializbale這個接口就可以了,具體內部實現一直不是很了解,正好這次在重復造RPC的輪子的時候涉及到序列化問題

    JAVA序列與反射

    技術分享 ges img .cn logs 技術 序列化 com -1 JAVA序列化與反射

    Java序列接口Serializable接口的作用總結

    生命周期 read 避免 什麽 打開 序列號 依賴 為什麽 main 轉載 http://www.cnblogs.com/DreamDrive/p/4005966.html 一個對象有對應的一些屬性,把這個對象保存在硬盤上的過程叫做”持久化”. 把堆內存中的對象的生命周期延

    Java序列Serializable和Externalizable

    持久化對象 clu version catch 例程 對象 uri put one 紙上得來終覺淺,絕知此事要躬行 --陸遊 問渠那得清如許,為有源頭活水來 --朱熹 什麽是Java序列化?為什麽出現Java序列化?如何實現Java序列化? 一、