定義:
Java序列化是指把Java對象轉換爲字節序列的過程,保存在硬盤中;而Java反序列化是指把字節序列恢復爲Java對象的過程。
優點:
好處一是實現了數據的持久化,通過序列化可以把數據永久地保存到硬盤上(通常存放在文件裏),二是,利用序列化實現遠程通信,即在網絡上傳送對象的字節序列。
JDK類庫中序列化API
java.io.ObjectOutputStream:表示對象輸出流
它的writeObject(Object obj)方法可以對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。(將對象序列化保存到文件中)
java.io.ObjectInputStream:表示對象輸入流
它的readObject()方法源輸入流中讀取字節序列,再把它們反序列化成爲一個對象,並將其返回。
簡單例子
1、先創建一個枚舉類
public enum Gender {
MALE,FEMALE
}
2、創建一個Person類。
注意:
如果被寫對象的類型是String,或數組,或Enum,或Serializable,那麼就可以對該對象進行序列化,否則將拋出NotSerializableException。
序列化是指對象的序列化,只會關注對象的屬性,不會關注類中的靜態變量
public class Person implements Serializable{
private String name;
private Integer age;
private 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;
}
public Person() {
System.out.println("none-arg constructor");
}
public Person(String name, Integer age, Gender gender) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "[" + name + ", " + age + ", " + gender + "]";
}
}
3、創建一個測試類:
public class SimpleSerialTest {
public static void main(String[] args) {
//新建一個文件
File file = new File("person.out");
try {
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
Person person = new Person("john", 21, Gender.MALE);
oout.writeObject(person);
oout.close();
//但必須確保該讀取程序的CLASSPATH中包含有Person.class
//(哪怕在讀取Person對象時並沒有顯示地使用Person類,如上例所示),否則會拋出ClassNotFoundException。
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object per = oin.readObject();
oin.close();
System.out.println(per.toString());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
上述程序的輸出結果:
arg constructor
[john, 21, MALE]
*此時必須注意的是,當重新讀取被保存的Person對象時,並沒有調用Person的任何構造器,看起來就像是直接使用字節將Person對象還原出來的。
當Person對象被保存到person.out文件中之後,我們可以在其它地方去讀取該文件以還原對象,但必須確保該讀取程序的CLASSPATH中包含有Person.class(哪怕在讀取Person對象時並沒有顯示地使用Person類,如上例所示),否則會拋出ClassNotFoundException。*
默認的序列化機制
一個類被序列化,不僅他本身被序列化,還會對該對象引用的其他對象也將被序列化,所以當一個類對象引用了某個容器類對象,而容器對象所包含的元素也是容器對象,當被序列化的時候,會出現將容器對象也序列化,此時會帶來很大的內存開銷。
在現實應用中,有些時候不能使用默認序列化機制。比如,希望在序列化過程中忽略掉敏感數據,或者簡化序列化過程。下面將介紹若干影響序列化的方法。如:transient關鍵字、writeObject()方法與readObject()方法指定那些屬性被序列化和輸出、Externalizable接口、readResolve()方法。
1、transient關鍵字
當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段。
/**
* 測試transient功能
* @author DELL
*
*/
public class Animal implements Serializable{
private String name;
private int age;
//使用transient,目的是爲了避免敏感信息在序列化的時候
transient private String getter;//持有者
//測試static類熟悉是否被序列化
public static int weight;
public void setGetter(String getter) {
this.getter = getter;
}
......省略set,get,構造方法
@Override
public String toString() {
return "Animal [name=" + name + ", age=" + age + ", getter=" + getter
+ "]";
}
}
測試類
public class Test {
public static void main(String[] args) {
File fileAnimal = new File("animal.out");
writeFile(fileAnimal, new Animal("niuniu", 3, "張三"));
readFile(fileAnimal);
}
//序列化公共處理方法
public static void writeFile(File file,Object obj){
try {
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
//Person person = new Person("zhangfei", 23, Gender.FEMALE);
oout.writeObject(obj);
oout.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//反序列化公共處理方法
public static void readFile(File file){
try {
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object obj = oin.readObject();
oin.close();
System.out.println(obj.toString());
//System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
測試結果:
Animal [name=niuniu, age=3, getter=null]
結果可見:Animal類對象中被transient修飾的持有者未被序列化。
writeObject()方法與readObject()方法
public class Person implements Serializable{
.......
transient private Integer age;
......
//如果使用了transient關鍵字,則該屬性在序列化時會被忽略,使用私有的writeObject,readObject方法,會讓被忽略的屬性,再次被序列化。
private void writeObject(ObjectOutputStream outputStream){
try {
outputStream.defaultWriteObject();
System.out.println("this is a flag");
outputStream.writeInt(age);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void readObject(ObjectInputStream inputStream){
try {
inputStream.defaultReadObject();
age = inputStream.readInt();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
測試代碼:
public class Test {
public static void main(String[] args) {
File file = new File("test.out");
writeFile(file,new Person("zhangfei", 23, Gender.FEMALE));
readFile(file);
}
......
}
測試結果:
arg constructor
this is a flag
[zhangfei, 23, FEMALE]
writeObject()方法中會先調用ObjectOutputStream中的defaultWriteObject()方法,該方法會執行默認的序列化機制,如5.1節所述,此時會忽略掉age字段。然後再調用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中。readObject()的作用則是針對對象的讀取,將值設置到屬性中,其原理與writeObject()方法相同
Externalizable接口
無論是使用transient關鍵字,還是使用writeObject()和readObject()方法,其實都是基於Serializable接口的序列化。JDK中提供了另一個序列化接口–Externalizable,使用該接口之後,之前基於Serializable接口的序列化機制就將失效。
/**
* 使用Externalizable接口,調用的是無參構造方法構造對象。
* @author DELL
*
*/
public class Project implements Externalizable{
private String projectName;
private String PorjectLeader;
private int teamPerson;//團隊人數
......
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
}
public Project() {
System.out.println("this method had constructor");
}
@Override
public String toString() {
......
}
}
測試代碼:
public class Test {
public static void main(String[] args) {
File fileProject = new File(“project.out”);
writeFile(fileProject, new Project(“XXXXXX”, “xiaoming”, 6));
readFile(fileProject);
}
}
輸出結果:
this method had constructor
Project [projectName=null, PorjectLeader=null, teamPerson=0]
從該結果方面,一個字段都沒有被序列化,Externalizable繼承於Serializable,當使用該接口時,序列化的細節需要由程序員去完成。如上所示的代碼,由於writeExternal()與readExternal()方法未作任何處理,那麼該序列化行爲將不會保存/讀取任何一個字段。這也就是爲什麼輸出結果中所有字段的值均爲空。
另外,使用Externalizable進行序列化時,當讀取對象時,會調用被序列化類的無參構造器去創建一個新的對象,然後再將被保存對象的字段的值分別填充到新對象中。這就是爲什麼在此次序列化過程中Project類的無參構造器會被調用。由於這個原因,實現Externalizable接口的類必須要提供一個無參的構造器,且它的訪問權限爲public。
因此對上面的project類做進一步修改:
public class Project implements Externalizable{
......將writeExternal,readExternal進行補全。。
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(projectName);
out.writeInt(teamPerson);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
projectName = (String) in.readObject();
teamPerson = in.readInt();
}
......
再次執行Test類,執行結果如下:
this method had constructor
Project [projectName=xxxxx, PorjectLeader=null, teamPerson=6]
readResolve()方法
public class SinglePerson implements Serializable{
private String name;
private String address;
private int age;
private static class InstanceHolder{
private static final SinglePerson person = new SinglePerson("jack", "tianfulu", 12);
}
public static SinglePerson getInstance(){
return InstanceHolder.person;
}
......
@Override
public String toString() {
return "SinglePerson [name=" + name + ", address=" + address + ", age="
+ age + "]";
}
}
測試代碼:
public class Test {
public static void main(String[] args) {
/*File file = new File("test.out");
writeFile(file,new Person("zhangfei", 23, Gender.FEMALE));
readFile(file);
File fileAnimal = new File("animal.out");
writeFile(fileAnimal, new Animal("niuniu", 3, "張三"));
readFile(fileAnimal);
File fileProject = new File("project.out");
writeFile(fileProject, new Project("xxxxx", "xiaoming", 6));
readFile(fileProject);*/
File fileSinglePerson = new File("singlePerson.out");
writeFile(fileSinglePerson, SinglePerson.getInstance());
readFile(fileSinglePerson);
}
......
//反序列化公共處理方法
public static void readFile(File file){
try {
.......
//新增
System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
測試結果:
SinglePerson [name=jack, address=tianfulu, age=12]
false
新增readResolve方法
public class SinglePerson implements Serializable{
private String name;
private String address;
private int age;
..........省略
private Object readResolve() throws ObjectStreamException{
return SinglePerson.getInstance();
}
}
再次執行測試代碼:
SinglePerson [name=jack, address=tianfulu, age=12]
true
無論是實現Serializable接口,或是Externalizable接口,當從I/O流中讀取對象時,readResolve()方法都會被調用到。實際上就是用readResolve()中返回的對象直接替換在反序列化過程中創建的對象。
參考文章:http://developer.51cto.com/art/201202/317181.htm
http://blog.csdn.net/wangloveall/article/details/7992448/