Serilizable 和 Externalizable 學(xué)習(xí)
本文是我對Java對象序列化的學(xué)習(xí)與理解
1、什么是Java對象序列化
Java平臺允許我們在內(nèi)存中創(chuàng)建可復(fù)用的Java對象,但一般情況下,只有當(dāng)JVM處于運(yùn)行狀態(tài)時(shí),這些對象才能存在,即,這些對象的生命周期不會比JVM更長。但在現(xiàn)實(shí)中,就可能要求在JVM運(yùn)行停止后能夠保存(持久化),以便下次運(yùn)行時(shí)重新獲取對象,Java對象序列化就能夠幫助我們實(shí)現(xiàn)該功能。
使用Java對象序列化保存對象時(shí),會把其狀態(tài)保存為一組字節(jié),在下次調(diào)用時(shí),再將這些字節(jié)裝入對象。需要注意的是,對象序列化保存的是對象的“狀態(tài)”,即它的成員變量。喲偶次可知,對象序列化不會關(guān)注類中的靜態(tài)變量。
除了在持久化對象時(shí)會用到對象序列化外,當(dāng)使用RMI(遠(yuǎn)程方法調(diào)用),或者網(wǎng)絡(luò)傳遞對象時(shí),也會用到對象序列化。Java序列化API為處理對象序列化提供了一個(gè)標(biāo)準(zhǔn)機(jī)制,簡單易用。
2、實(shí)現(xiàn)序列化的步驟
a) Make a FileOutputStream
FileOutputStream fs = new FileOutputStream("foo.ser");
b) Make a ObjectOutputStram
ObjectOutputStream os = new ObjectOutputStream(fs);
c) Write to object
os.writeObject(myObject1);
os.writeObject(myObject2);
os.writeObject(myObject3);
d) Close the ObjectOutputStram
os.close();
3、簡單實(shí)例
在Java中,一個(gè)類只需要實(shí)現(xiàn)java.ioo.Serilizable接口,那么它就可以被序列化,看下面一個(gè)簡單示例:
在Person.java類中Gender為一個(gè)枚舉類型,表示性別,大家應(yīng)該了解每個(gè)枚舉類型默認(rèn)繼承類java.lang.Enum,而該類實(shí)現(xiàn)了Serializable接口,所以默認(rèn)的所有枚舉類型都可以被序列化。
在Person中有三個(gè)私有成員變量,name,age,gender,實(shí)現(xiàn)了Serializable接口,
enum Gender{
MALE,FEMALE
}
public class Person implements Serializable
{
private String name;
private int age;
private Gender gender;
public Person(){
System.out.println("Person無參構(gòu)造函數(shù)");
}
public Person(String name,int age,Gender gender) {
System.out.println("Person有參構(gòu)造函數(shù)");
this.name = name;
this.age = age;
this.gender = gender;
}
// 省略get和set方法以及toString方法
}
SimpleSerial.java是程序的入口,先將Person對象保存到person.out中,然后再從該文件中讀取到Person對象中,并打印出結(jié)果。
public class SimpleSerial {
public static void main(String[] args) {
File file = new File("person.out");
try {
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person person = new Person("susu",21,Gender.FEMALE);
oos.writeObject(person);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object operson = ois.readObject();
ois.close();
System.out.println(operson);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上述代碼的結(jié)果為:
Person有參構(gòu)造函數(shù)
Person [name=susu, age=21, gender=FEMALE]
注意:
- 此時(shí)需要注意的是,當(dāng)重新獲取被保存的Person對象時(shí),并沒有調(diào)用構(gòu)造方法,看起來像是直接使用字節(jié)將對象還原出來。
- 當(dāng)Person對象保存到person.out文件后,我們可以在其他地方讀取到該文件的還原對象,但必須確保取程序的CLASSPATH中包含Person.class。
4、默認(rèn)序列化機(jī)制
如果僅僅只是讓某個(gè)類實(shí)現(xiàn)Serializable接口,而沒有其他處理的話,則就是使用默認(rèn)序列化機(jī)制。使用默認(rèn)機(jī)制,在序列化對象時(shí),不僅會序列化當(dāng)前對象本身,還會對該對象引用的其他對象也進(jìn)行序列化,同樣的,這些其他對象引用的另外對象也將被序列化,以此類推。所以,如果一個(gè)對象的成員變量是容器類對象,而這些容器含有的元素也是容器類對象,那么這個(gè)序列化對象的過程就會很復(fù)雜,開銷也很大。
5、影響序列化
在現(xiàn)實(shí)中,有些是默認(rèn)不使用序列化的,比如,希望在序列化過程中忽略掉敏感數(shù)據(jù),或者簡化序列化過程。下面介紹相關(guān)方法:
5.1、使用transient關(guān)鍵字
當(dāng)某字段使用transient關(guān)鍵字后,默認(rèn)化序列將會忽略該字段。此處將Person中的age聲明為transient,如下所示:
public class Person implements Serializable //Externalizable
{
......
transient private int age;
......
}
運(yùn)行結(jié)果為:
Person有參構(gòu)造函數(shù)
Person [name=susu, age=0, gender=FEMALE]
可以看出,沒有獲取到age的值,
5.2、writeObject()方法與readObject()方法
對于上述已經(jīng)被stransitive序列化的字段,除了將stransitive關(guān)鍵字去掉以外,還可以用writeObject()方法與readObject()方法再次序列化age。先上代碼吧:
public class Person implements Serializable
{
......
transient private int age;
......
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()方法中會先調(diào)用ObjectOutputStream中的defaultWriteObject()方法,該方法會執(zhí)行默認(rèn)的序列化機(jī)制,此時(shí)對忽略掉age字段,然后調(diào)用writeInt()方法顯示將age字段寫入到ObjectOutputStream中,readObject()的作用是針對對象的讀取,其原理與writeObject()方法相同。
再次運(yùn)行,結(jié)果如下:
Person有參構(gòu)造函數(shù)
Person [name=susu, age=21, gender=FEMALE]
6、 Enternalizable接口
在JDK中,提供了另外一種序列化的接口--Enterbalizable,使用該接口后,以前基于Serializable接口的序列化機(jī)制將會無效。
其實(shí)在這里,我有個(gè)疑問,我查看了文檔,發(fā)現(xiàn)Enternalizable繼承自Serializable,但為什么這里會失效呢,目前還不是很懂。
Person改為實(shí)現(xiàn)Enternalizable接口后代碼如下:
public class Person implements Externalizable {
private String name;
transient private int age;
private Gender gender;
public Person(){
System.out.println("Person無參構(gòu)造函數(shù)");
}
public Person(String name,int age,Gender gender) {
System.out.println("Person有參構(gòu)造函數(shù)");
this.name = name;
this.age = age;
this.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();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
}
// 省略get和set,toString方法
}
運(yùn)行結(jié)果如下:
Person有參構(gòu)造函數(shù)
Person無參構(gòu)造函數(shù)
Person [name=null, age=0, gender=null]
從運(yùn)行結(jié)果來看,一方面看出Person對象中任何一個(gè)字段都沒有被序列化。另一方面,發(fā)現(xiàn)調(diào)用了Person的無參構(gòu)造方法。
Externalizable繼承于Serializable,當(dāng)使用該接口時(shí),序列化的細(xì)節(jié)需要由程序員去完成。如上所示的代碼,由于writeExternal()與readExternal()方法未作任何處理,那么該序列化行為將不會保存/讀取任何一個(gè)字段。這也就是為什么輸出結(jié)果中所有字段的值均為空。
另外,若使用Externalizable進(jìn)行序列化,當(dāng)讀取對象時(shí),會調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個(gè)新的對象,然后再將被保存對象的字段的值分別填充到新對象中。這就是為什么在此次序列化過程中Person類的無參構(gòu)造器會被調(diào)用。由于這個(gè)原因,實(shí)現(xiàn)Externalizable接口的類必須要提供一個(gè)無參的構(gòu)造器,且它的訪問權(quán)限為public。
對上述Person類作進(jìn)一步的修改,使其能夠?qū)ame與age字段進(jìn)行序列化,但要忽略掉gender字段,如下代碼所示:
public class Person implements Externalizable //Serializable
{
......
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
}
運(yùn)行結(jié)果如下:
Person有參構(gòu)造函數(shù)
Person無參構(gòu)造函數(shù)
Person [name=susu, age=21, gender=null]