Java Serializable(序列化)的理解和總結

參考資料:http://www.cnblogs.com/yangjian-java/p/7813623.htmlhtml

一、序列化是幹什麼的?java

       Java平臺容許咱們在內存中建立可複用的Java對象,但通常狀況下,只有當JVM處於運行時,這些對象纔可能存在,即,這些對象的生命週期不會比JVM的生命週期更長。數據庫

       但在現實應用中,就可能要求在JVM中止運行以後可以保存(持久化)指定的對象,並在未來從新讀取被保存的對象。Java對象序列化就可以幫助咱們實現該功能。網絡

       使用Java對象序列化,在保存對象時,會把其狀態保存爲一組字節,在將來,再將這些字節組裝成對象。必須注意地是,對象序列化保存的是對象的"狀態",即它的成員變量。由此可知,對象序列化不會關注類中的靜態變量!!!jvm

       除了在持久化對象時會用到對象序列化以外,當使用RMI(遠程方法調用),或在網絡中傳遞對象時,都會用到對象序列化。Java序列化API爲處理對象序列化提供了一個標準機制,該API簡單易用,之後筆者會逐步介紹之。ide

二、什麼狀況下須要序列化   
      
a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
       b)當你想用套接字在網絡上傳送對象的時候;
       c)當你想經過RMI傳輸對象的時候;測試

簡而言之:序列化的做用就是爲了避免同jvm之間共享實例對象的一種解決方案.由java提供此機制,效率之高,是其餘解決方案沒法比擬的.自家的東西嘛.this

三、簡單示例spa

首先建立一個Person類debug

import java.io.Serializable;

public class Person implements Serializable{
    private String name;
    private Integer age;
    private Gender gender;
    
    public Person() {
        System.out.println("Persong no arg constructor");
    }

    public Person(String name, Integer age, Gender gender) {
        System.out.println("Persong all args constructor");
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]";
    }
}

PS:該類必須實現Serializable接口,不然會報java.io.NotSerializableException: serializable.Person的錯誤(讀者不仿一會試試去掉Serializable試試)

其中,Gender是個枚舉類

/**
 *  性別枚舉類
 */
public enum Gender {
   MALE,FEMALE;
}

/**
 * 每一個枚舉類型都會默認繼承類java.lang.Enum,而該類實現了Serializable接口,因此枚舉類型對象都是默承認以被序列化的
 */ 

下面,就是測試代碼:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SimpleSerializableTest {
    public static void main(String[] args)throws Exception { 
        //首先,指定輸出文件
        File file = new File("person.out");
        //建立一個FileOutputStream
        FileOutputStream fo = new FileOutputStream(file);
        //建立一個ObjectOutputStream
        ObjectOutputStream    oo = new ObjectOutputStream(fo);
        //建立一個Person對象
        Person person = new Person("李同窗",18,Gender.MALE);
        oo.writeObject(person);
        oo.close();
        
        //再將person讀出來
        //建立一個ObjectInputStream
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream(file));
        Object readObject = oi.readObject();
        oi.close();
        System.out.println(readObject);
    }
}

運行這段代碼,你將看到以下輸出:

Persong all args constructor  //這個是在建立Person的時候調用的
Person [name=李同窗, age=18, gender=MALE] //這個實在最後打印readObject的時候調用的

咱們能夠看到,咱們成功將建立的對象寫入到person.out文件中,而後經過readObject將其讀出來(是讀出一個已序列化的對象,即直接使用字節將Person對象還原出來的,而不是new一個,能夠看到也沒有調用構造器嘛)

然,當Person對象被保存到person.out文件中以後,咱們能夠在其它地方去讀取該文件以還原對象,以下面代碼:

import java.io.File;
import java.io.FileInputStream; 
import java.io.ObjectInputStream; 

public class OtherReadPersonSerializable {
    public static void main(String[] args)throws Exception {  
        File file = new File("person.out");
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream(file));
        Object otherRead = oi.readObject();
        oi.close();
        System.out.println(otherRead);
    }
}

仍然能夠輸入以下內容:

Person [name=李同窗, age=18, gender=MALE]

但必須確保該讀取程序的CLASSPATH中包含有Person.class(哪怕在讀取Person對象時並無顯示地使用Person類,如上例所示),不然會拋出ClassNotFoundException。

四、Serializable的做用

      爲何一個類實現了Serializable接口,它就能夠被序列化呢?在上節的示例中,使用ObjectOutputStream來持久化對象,在該類中有以下代碼:

 1           if (obj instanceof String) {
 2                 writeString((String) obj, unshared);
 3             } else if (cl.isArray()) {
 4                 writeArray(obj, desc, unshared);
 5             } else if (obj instanceof Enum) {
 6                 writeEnum((Enum<?>) obj, desc, unshared);
 7             } else if (obj instanceof Serializable) {
 8                 writeOrdinaryObject(obj, desc, unshared);
 9             } else {
10                 if (extendedDebugInfo) {
11                     throw new NotSerializableException(
12                         cl.getName() + "\n" + debugInfoStack.toString());
13                 } else {
14                     throw new NotSerializableException(cl.getName());
15                 }
16             }

看代碼第7行,作了Serializable類型判斷,從這段代碼中也能夠知道,能夠被序列化的是String,Array,Enum以及實現了Serializable接口的類

五、默認序列化機制
        若是僅僅只是讓某個類實現Serializable接口,而沒有其它任何處理的話,則就是使用默認序列化機制。使用默認機制,在序列化對象時,不只會序列化當前對象自己,還會對該對象引用的其它對象也進行序列化,一樣地,這些其它對象引用的另外對象也將被序列化,以此類推。因此,若是一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那麼這個序列化的過程就會較複雜,開銷也較大。

六、 影響序列化
    在現實應用中,有些時候不能使用默認序列化機制。好比,但願在序列化過程當中忽略掉敏感數據,或者簡化序列化過程。下面將介紹若干影響序列化的方法。

    6.1 transient關鍵字

    當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段。好比,將Person中age生命爲transient:

transient private Integer age;

    那麼,當你再運行SimpleSerializableTest 的時候,就會有以下輸出:

Persong all args constructor
Person [name=李同窗, age=null, gender=MALE]

   你看,這個時候age就爲null了,未被序列化到person.out中。

   6.2 writeObject()方法與readObject()方法

    對於上述已被聲明爲transitive的字段age,除了將transitive關鍵字去掉以外,是否還有其它方法能使它再次可被序列化?方法之一就是在Person類中添加兩個方法:writeObject()與readObject(),以下所示:

public class Person implements Serializable {
    private String name;
    transient private Integer age; //未去掉transient
    private Gender gender;

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        age = in.readInt();
    }
       ...
       // 其餘不變
}

       在writeObject()方法中會先調用ObjectOutputStream中的defaultWriteObject()方法,該方法會執行默認的序列化機制,如6.1節所述,此時會忽略掉age字段。而後再調用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中。readObject()的做用則是針對對象的讀取,其原理與writeObject()方法相同。

Persong all args constructor
Person [name=李同窗, age=18, gender=MALE]

      必須注意地是,writeObject()與readObject()都是private方法,那麼它們是如何被調用的呢?毫無疑問,是使用反射。詳情可見ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。

相關文章
相關標籤/搜索