Java對象表示方式1:序列化、反序列化的做用

1.序列化是的做用和用途html

序列化:把對象轉換爲字節序列的過程稱爲對象的序列化java

反序列化:把字節序列恢復爲對象的過程稱爲對象的反序列化安全

對象的序列化主要有兩種用途:
  1) 把對象的字節序列永久地保存到硬盤上,一般存放在一個文件中;
  2) 在網絡上傳送對象的字節序列。
網絡

2.序列化的步驟ide

       java.io.ObjectOutputStream表明對象輸出流,它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把獲得的字節序列寫到一個目標輸出流中。
  java.io.ObjectInputStream表明對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化爲一個對象,並將其返回。
  只有實現了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自 Serializable接口,實現Externalizable接口的類徹底由自身來控制序列化的行爲,而僅實現Serializable接口的類能夠 採用默認的序列化方式 。
  對象序列化包括以下步驟:
  1) 建立一個對象輸出流,它能夠包裝一個其餘類型的目標輸出流,如文件輸出流;
  2) 經過對象輸出流的writeObject()方法寫對象。

  對象反序列化的步驟以下:
  1) 建立一個對象輸入流,它能夠包裝一個其餘類型的源輸入流,如文件輸入流;
  2) 經過對象輸入流的readObject()方法讀取對象。
函數

3.默認的序列化測試

序列化只須要實現java.io.Serializable接口就能夠了。序列化的時候有一個serialVersionUID參數,Java序列化機制是經過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化,Java虛擬機會把傳過來的字節流中的serialVersionUID和本地相應實體類的serialVersionUID進行比較,若是相同就認爲是一致的實體類,能夠進行反序列化,不然Java虛擬機會拒絕對這個實體類進行反序列化並拋出異常。serialVersionUID有兩種生成方式:this

一、默認的1L加密

二、根據類名、接口名、成員方法以及屬性等來生成一個64位的Hash字段spa

若是實現java.io.Serializable接口的實體類沒有顯式定義一個名爲serialVersionUID、類型爲long的變量時,Java序列化機制會根據編譯的.class文件自動生成一個serialVersionUID,若是.class文件沒有變化,那麼就算編譯再屢次,serialVersionUID也不會變化。換言之,Java爲用戶定義了默認的序列化、反序列化方法,其實就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法看一個例子:

複製代碼
 1 public class SerializableObject implements Serializable
 2 {
 3     private static final long serialVersionUID = 1L;
 4     
 5     private String str0;
 6     private transient String str1;
 7     private static String str2 = "abc";
 8     
 9     public SerializableObject(String str0, String str1)
10     {
11         this.str0 = str0;
12         this.str1 = str1;
13     }
14     
15     public String getStr0()
16     {
17         return str0;
18     }
19 
20     public String getStr1()
21     {
22         return str1;
23     }
24 }
複製代碼
複製代碼
 1 public static void main(String[] args) throws Exception
 2 {
 3     File file = new File("D:" + File.separator + "s.txt");
 4     OutputStream os = new FileOutputStream(file);  
 5     ObjectOutputStream oos = new ObjectOutputStream(os);
 6     oos.writeObject(new SerializableObject("str0", "str1"));
 7     oos.close();
 8         
 9     InputStream is = new FileInputStream(file);
10     ObjectInputStream ois = new ObjectInputStream(is);
11     SerializableObject so = (SerializableObject)ois.readObject();
12     System.out.println("str0 = " + so.getStr0());
13     System.out.println("str1 = " + so.getStr1());
14     ois.close();
15 }
複製代碼

先不運行,用一個二進制查看器查看一下s.txt這個文件,並詳細解釋一下每一部分的內容。

第1部分是序列化文件頭

◇AC ED:STREAM_MAGIC序列化協議

◇00 05:STREAM_VERSION序列化協議版本

◇73:TC_OBJECT聲明這是一個新的對象

第2部分是要序列化的類的描述,在這裏是SerializableObject類

◇72:TC_CLASSDESC聲明這裏開始一個新的class

◇00 1F:十進制的31,表示class名字的長度是31個字節

◇63 6F 6D ... 65 63 74:表示的是「com.xrq.test.SerializableObject」這一串字符,能夠數一下確實是31個字節

◇00 00 00 00 00 00 00 01:SerialVersion,序列化ID,1

◇02:標記號,聲明該對象支持序列化

◇00 01:該類所包含的域的個數爲1個

第3部分是對象中各個屬性項的描述

◇4C:字符"L",表示該屬性是一個對象類型而不是一個基本類型

◇00 04:十進制的4,表示屬性名的長度

◇73 74 72 30:字符串「str0」,屬性名

◇74:TC_STRING,表明一個new String,用String來引用對象

第4部分是該對象父類的信息,若是沒有父類就沒有這部分。有父類和第2部分差很少

◇00 12:十進制的18,表示父類的長度

◇4C 6A 61 ... 6E 67 3B:「L/java/lang/String;」表示的是父類屬性

◇78:TC_ENDBLOCKDATA,對象塊結束的標誌

◇70:TC_NULL,說明沒有其餘超類的標誌

第5部分輸出對象的屬性項的實際值,若是屬性項是一個對象,這裏還將序列化這個對象,規則和第2部分同樣

◇00 04:十進制的4,屬性的長度

◇73 74 72 30:字符串「str0」,str0的屬性值

從以上對於序列化後的二進制文件的解析,咱們能夠得出如下幾個關鍵的結論:

 

一、序列化以後保存的是類的信息

二、被聲明爲transient的屬性不會被序列化,這就是transient關鍵字的做用

三、被聲明爲static的屬性不會被序列化,這個問題能夠這麼理解,序列化保存的是對象的狀態,可是static修飾的變量是屬於類的而不是屬於變量的,所以序列化的時候不會序列化它

接下來運行一下上面的代碼看一下

str0 = str0
str1 = null

由於str1是一個transient類型的變量,沒有被序列化,所以反序列化出來也是沒有任何內容的,顯示的null,符合咱們的結論。

 4.defaultWriteObject和defaultReadObject(手動指定序列化)

4.1手動指定序列化

Java並不強求用戶非要使用默認的序列化方式,用戶也能夠按照本身的喜愛本身指定本身想要的序列化方式----只要你本身能保證序列化先後能獲得想要的數據就行了。手動指定序列化方式的規則是:

進行序列化、反序列化時,虛擬機會首先試圖調用對象裏的writeObject和readObject方法,進行用戶自定義的序列化和反序列化。若是沒有這樣的方法,那麼默認調用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法。換言之,利用自定義的writeObject方法和readObject方法,用戶能夠本身控制序列化和反序列化的過程。

這是很是有用的。好比:

一、有些場景下,某些字段咱們並不想要使用Java提供給咱們的序列化方式,而是想要以自定義的方式去序列化它,好比ArrayList的elementData、HashMap的table(至於爲何在以後寫這兩個類的時候會解釋緣由),就能夠經過將這些字段聲明爲transient,而後在writeObject和readObject中去使用本身想要的方式去序列化它們

二、由於序列化並不安全,所以有些場景下咱們須要對一些敏感字段進行加密再序列化,而後再反序列化的時候按照一樣的方式進行解密,就在必定程度上保證了安全性了。要這麼作,就必須本身寫writeObject和readObject,writeObject方法在序列化前對字段加密,readObject方法在序列化以後對字段解密

上面的例子SerializObject這個類修改一下,主函數不須要修改:

複製代碼
 1 public class SerializableObject implements Serializable
 2 {
 3     private static final long serialVersionUID = 1L;
 4     
 5     private String str0;
 6     private transient String str1;
 7     private static String str2 = "abc";
 8     
 9     public SerializableObject(String str0, String str1)
10     {
11         this.str0 = str0;
12         this.str1 = str1;
13     }
14     
15     public String getStr0()
16     {
17         return str0;
18     }
19 
20     public String getStr1()
21     {
22         return str1;
23     }
24     
25     private void writeObject(java.io.ObjectOutputStream s) throws Exception
26     {
27         System.out.println("我想本身控制序列化的過程");
28         s.defaultWriteObject();
29         s.writeInt(str1.length());
30         for (int i = 0; i < str1.length(); i++)
31             s.writeChar(str1.charAt(i));
32     }
33     
34     private void readObject(java.io.ObjectInputStream s) throws Exception
35     {
36         System.out.println("我想本身控制反序列化的過程");
37         s.defaultReadObject();
38         int length = s.readInt();
39         char[] cs = new char[length];
40         for (int i = 0; i < length; i++)
41             cs[i] = s.readChar();
42         str1 = new String(cs, 0, length);
43     }
44 }
複製代碼

直接看一下運行結果:

我想本身控制序列化的過程
我想本身控制反序列化的過程
str0 = str0
str1 = str1

看到,程序走到了咱們本身寫的writeObject和readObject中,並且被transient修飾的str1也成功序列化、反序列化出來了----由於手動將str1寫入了文件和從文件中讀了出來。不妨再看一下s.txt文件的二進制:

看到橘黃色的部分就是writeObject方法追加的str1的內容。至此,總結一下writeObject和readObject的一般用法:

先經過defaultWriteObject和defaultReadObject方法序列化、反序列化對象,而後在文件結尾追加須要額外序列化的內容/從文件的結尾讀取額外須要讀取的內容。 

 4.2 經過這種方式達到 序列化static和transient變量的目的

 

1 /**
  2  * 序列化的演示測試程序
  3  *
  4  * @author skywang
  5  */
  6 
  7 import java.io.FileInputStream;   
  8 import java.io.FileOutputStream;   
  9 import java.io.ObjectInputStream;   
 10 import java.io.ObjectOutputStream;   
 11 import java.io.Serializable;   
 12 import java.io.IOException;   
 13 import java.lang.ClassNotFoundException;   
 14   
 15 public class SerialTest5 { 
 16     private static final String TMP_FILE = ".serialtest5.txt";
 17   
 18     public static void main(String[] args) {   
 19         // 將「對象」經過序列化保存
 20         testWrite();
 21         // 將序列化的「對象」讀出來
 22         testRead();
 23     }
 24   
 25 
 26     /**
 27      * 將Box對象經過序列化,保存到文件中
 28      */
 29     private static void testWrite() {   
 30         try {
 31             // 獲取文件TMP_FILE對應的對象輸出流。
 32             // ObjectOutputStream中,只能寫入「基本數據」或「支持序列化的對象」
 33             ObjectOutputStream out = new ObjectOutputStream(
 34                     new FileOutputStream(TMP_FILE));
 35             // 建立Box對象,Box實現了Serializable序列化接口
 36             Box box = new Box("desk", 80, 48);
 37             // 將box對象寫入到對象輸出流out中,即至關於將對象保存到文件TMP_FILE中
 38             out.writeObject(box);
 39             // 打印「Box對象」
 40             System.out.println("testWrite box: " + box);
 41             // 修改box的值
 42             box = new Box("room", 100, 50);
 43 
 44             out.close();
 45         } catch (Exception ex) {
 46             ex.printStackTrace();
 47         }
 48     }
 49  
 50     /**
 51      * 從文件中讀取出「序列化的Box對象」
 52      */
 53     private static void testRead() {
 54         try {
 55             // 獲取文件TMP_FILE對應的對象輸入流。
 56             ObjectInputStream in = new ObjectInputStream(
 57                     new FileInputStream(TMP_FILE));
 58             // 從對象輸入流中,讀取先前保存的box對象。
 59             Box box = (Box) in.readObject();
 60             // 打印「Box對象」
 61             System.out.println("testRead  box: " + box);
 62             in.close();
 63         } catch (Exception e) {
 64             e.printStackTrace();
 65         }
 66     }
 67 }
 68 
 69 
 70 /**
 71  * Box類「支持序列化」。由於Box實現了Serializable接口。
 72  *
 73  * 實際上,一個類只須要實現Serializable便可實現序列化,而不須要實現任何函數。
 74  */
 75 class Box implements Serializable {
 76     private static int width;   
 77     private transient int height; 
 78     private String name;   
 79 
 80     public Box(String name, int width, int height) {
 81         this.name = name;
 82         this.width = width;
 83         this.height = height;
 84     }
 85 
 86     private void writeObject(ObjectOutputStream out) throws IOException{ 
 87         out.defaultWriteObject();//使定製的writeObject()方法能夠利用自動序列化中內置的邏輯。 
 88         out.writeInt(height); 
 89         out.writeInt(width); 
 90         //System.out.println("Box--writeObject width="+width+", height="+height);
 91     }
 92 
 93     private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ 
 94         in.defaultReadObject();//defaultReadObject()補充自動序列化 
 95         height = in.readInt(); 
 96         width = in.readInt(); 
 97         //System.out.println("Box---readObject width="+width+", height="+height);
 98     }
 99 
100     @Override
101     public String toString() {
102         return "["+name+": ("+width+", "+height+") ]";
103     }
104 }

 

  

運行結果

testWrite box: [desk: (80, 48) ]
testRead  box: [desk: (80, 48) ]

程序說明

「序列化不會自動保存static和transient變量」,所以咱們若要保存它們,則須要經過writeObject()和readObject()去手動讀寫。
(01) 經過writeObject()方法,寫入要保存的變量。writeObject的原始定義是在ObjectOutputStream.java中,咱們按照以下示例覆蓋便可:

private void writeObject(ObjectOutputStream out) throws IOException{ 
    out.defaultWriteObject();// 使定製的writeObject()方法能夠利用自動序列化中內置的邏輯。 
    out.writeInt(ival);      // 若要保存「int類型的值」,則使用writeInt()
    out.writeObject(obj);    // 若要保存「Object對象」,則使用writeObject()
}

(02) 經過readObject()方法,讀取以前保存的變量。readObject的原始定義是在ObjectInputStream.java中,咱們按照以下示例覆蓋便可:

private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ 
    in.defaultReadObject();       // 使定製的readObject()方法能夠利用自動序列化中內置的邏輯。 
    int ival = in.readInt();      // 若要讀取「int類型的值」,則使用readInt()
    Object obj = in.readObject(); // 若要讀取「Object對象」,則使用readObject()
}

至此,咱們就介紹完了「序列化對static和transient變量的處理」。

5.Externalizable 

若是一個類要徹底負責本身的序列化,則實現Externalizable接口,而不是Serializable接口。

Externalizable:他是Serializable接口的子類,有時咱們不但願序列化那麼多,可使用這個接口,這個接口的writeExternal()和readExternal()方法能夠指定序列化哪些屬性。

須要注意的是:聲明類實現Externalizable接口會有重大的安全風險。writeExternal()與readExternal()方法聲明爲public,惡意類能夠用這些方法讀取和寫入對象數據。若是對象包含敏感信息,則要格外當心。

/**
 * 序列化的演示測試程序
 *
 * @author skywang
 */

import java.io.FileInputStream;   
import java.io.FileOutputStream;   
import java.io.ObjectInputStream;   
import java.io.ObjectOutputStream;   
import java.io.ObjectOutput;   
import java.io.ObjectInput;   
import java.io.Serializable;   
import java.io.Externalizable;   
import java.io.IOException;   
import java.lang.ClassNotFoundException;   
  
public class ExternalizableTest2 { 
    private static final String TMP_FILE = ".externalizabletest2.txt";
  
    public static void main(String[] args) {   
        // 將「對象」經過序列化保存
        testWrite();
        // 將序列化的「對象」讀出來
        testRead();
    }
  

    /**
     * 將Box對象經過序列化,保存到文件中
     */
    private static void testWrite() {   
        try {
            // 獲取文件TMP_FILE對應的對象輸出流。
            // ObjectOutputStream中,只能寫入「基本數據」或「支持序列化的對象」
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(TMP_FILE));
            // 建立Box對象
            Box box = new Box("desk", 80, 48);
            // 將box對象寫入到對象輸出流out中,即至關於將對象保存到文件TMP_FILE中
            out.writeObject(box);
            // 打印「Box對象」
            System.out.println("testWrite box: " + box);

            out.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
 
    /**
     * 從文件中讀取出「序列化的Box對象」
     */
    private static void testRead() {
        try {
            // 獲取文件TMP_FILE對應的對象輸入流。
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(TMP_FILE));
            // 從對象輸入流中,讀取先前保存的box對象。
            Box box = (Box) in.readObject();
            // 打印「Box對象」
            System.out.println("testRead  box: " + box);
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


/**
 * Box類實現Externalizable接口
 */
class Box implements Externalizable {
    private int width;   
    private int height; 
    private String name;   

    public Box() {
    }

    public Box(String name, int width, int height) {
        this.name = name;
        this.width = width;
        this.height = height;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(width);
        out.writeInt(height);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        width = in.readInt();
        height = in.readInt();
    }

    @Override
    public String toString() {
        return "["+name+": ("+width+", "+height+") ]";
    }
}

  

運行結果

testWrite box: [desk: (80, 48) ]
testRead  box: [null: (0, 0) ]

注意事項:

(01) 實現Externalizable接口的類,不會像實現Serializable接口那樣,會自動將數據保存。
(02) 實現Externalizable接口的類,必須實現writeExternal()和readExternal()接口!不然,程序沒法正常編譯!
(03) 實現Externalizable接口的類,必須定義不帶參數的構造函數!會默認的調用構造函數,不然,程序沒法正常編譯!
(04) writeExternal() 和 readExternal() 的方法都是public的,不是很是安全!

 

6.複雜序列化狀況總結

雖然Java的序列化可以保證對象狀態的持久保存,可是遇到一些對象結構複雜的狀況仍是比較難處理的,最後對一些複雜的對象狀況做一個總結:

一、當父類繼承Serializable接口時,全部子類均可以被序列化

二、子類實現了Serializable接口,父類沒有,父類中的屬性不能序列化(不報錯,數據丟失),可是在子類中屬性仍能正確序列化

三、若是序列化的屬性是對象,則這個對象也必須實現Serializable接口,不然會報錯

四、反序列化時,若是對象的屬性有修改或刪減,則修改的部分屬性會丟失,但不會報錯

五、反序列化時,若是serialVersionUID被修改,則反序列化時會失敗

 

轉載:http://www.cnblogs.com/xrq730/p/4821958.html

http://www.cnblogs.com/skywang12345/p/io_06.html

相關文章
相關標籤/搜索