对象的序列化就是将对象写入输出流中。html
反序列化就是从输入流中将对象读取出来。java
用来实现序列化的类都在java.io包中,咱们经常使用的类或接口有:api
ObjectOutputStream:提供序列化对象并把其写入流的方法数组
ObjectInputStream:读取流并反序列化对象ide
Serializable:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的全部属性(包括private属性、包括其引用的对象)均可以被序列化和反序列化来保存、传递。函数
Externalizable:他是Serializable接口的子类,有时咱们不但愿序列化那么多,可使用这个接口,这个接口的writeExternal()和readExternal()方法能够指定序列化哪些属性;测试
可是若是你只想隐藏一个属性,好比用户对象user的密码pwd,若是使用Externalizable,并除了pwd以外的每一个属性都写在writeExternal()方法里,这样显得麻烦,可使用Serializable接口,并在要隐藏的属性pwd前面加上transient就能够实现了。this
方法一:spa
实现Serializable接口。.net
序列化的时候的一个关键字:transient(临时的)。它声明的变量实行序列化操做的时候不会写入到序列化文件中去。
例子:
package demo2; import java.io.Serializable; //实现Serializable接口才能被序列化 public class UserInfo implements Serializable{ private String userName; private String usePass; private transient int userAge;//使用transient关键字修饰的变量不会被序列化 public String getUserName() { return userName; } public UserInfo() { userAge=20; } public UserInfo(String userName, String usePass, int userAge) { super(); this.userName = userName; this.usePass = usePass; this.userAge = userAge; } public void setUserName(String userName) { this.userName = userName; } public String getUsePass() { return usePass; } public void setUsePass(String usePass) { this.usePass = usePass; } public int getUserAge() { return userAge; } public void setUserAge(int userAge) { this.userAge = userAge; } @Override public String toString() { return "UserInfo [userName=" + userName + ", usePass=" + usePass + ",userAge="+(userAge==0?"NOT SET":userAge)+"]"; } }
package demo2; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Date; public class UserInfoTest { /** * 序列化对象到文件 * @param fileName */ public static void serialize(String fileName){ try { ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName)); out.writeObject("序列化的日期是:");//序列化一个字符串到文件 out.writeObject(new Date());//序列化一个当前日期对象到文件 UserInfo userInfo=new UserInfo("郭大侠","961012",21); out.writeObject(userInfo);//序列化一个会员对象 out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 从文件中反序列化对象 * @param fileName */ public static void deserialize(String fileName){ try { ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName)); String str=(String) in.readObject();//刚才的字符串对象 Date date=(Date) in.readObject();//日期对象 UserInfo userInfo=(UserInfo) in.readObject();//会员对象 System.out.println(str); System.out.println(date); System.out.println(userInfo); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args){ // serialize("text"); deserialize("text");//这里userAge取读不到是由于使用了transient修饰,因此获得的是默认值 /** * 我修改了一下UserInfo的无参构造,在无参构造中给userAge属性赋值蛋反序列化获得的结果仍是同样。 * 得出结论: * 当从磁盘中读出某个类的实例时,实际上并不会执行这个类的构造函数, * 而是载入了一个该类对象的持久化状态,并将这个状态赋值给该类的另外一个对象。 */ } }
方法二:
实现Externalizable接口:
使用这个接口的场合是这样的:
一个类中咱们只但愿序列化一部分数据,其余数据都使用transient修饰的话显得有点麻烦,这时候咱们使用externalizable接口,指定序列化的属性。
例子:
package demo2;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
//实现Externalizable接口序列化
public class UserInfo implements Externalizable{
private String userName;
private String usePass;
private int userAge;
public String getUserName() {
return userName;
}
public UserInfo() {
userAge=20;//这个是在第二次测试使用,判断反序列化是否经过构造器
}
public UserInfo(String userName, String usePass, int userAge) {
super();
this.userName = userName;
this.usePass = usePass;
this.userAge = userAge;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUsePass() {
return usePass;
}
public void setUsePass(String usePass) {
this.usePass = usePass;
}
public int getUserAge() {
return userAge;
}
public void setUserAge(int userAge) {
this.userAge = userAge;
}
@Override
public String toString() {
return "UserInfo [userName=" + userName + ", usePass=" + usePass + ",userAge="+(userAge==0?"NOT SET":userAge)+"]";
}
public void writeExternal(ObjectOutput out) throws IOException {
/*
* 指定序列化时候写入的属性。这里仍然不写入年龄
*/
out.writeObject(userName);
out.writeObject(usePass);
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
/*
* 指定反序列化的时候读取属性的顺序以及读取的属性
* 若是你写反了属性读取的顺序,你能够发现反序列化的读取的对象的指定的属性值也会与你写的读取方式一一对应。由于在文件中装载对象是有序的
*/
userName=(String) in.readObject();
usePass=(String) in.readObject();
}
}
测试:
package demo2;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
public class UserInfoTest {
/**
* 序列化对象到文件
* @param fileName
*/
public static void serialize(String fileName){
try {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));
out.writeObject("序列化的日期是:");//序列化一个字符串到文件
out.writeObject(new Date());//序列化一个当前日期对象到文件
UserInfo userInfo=new UserInfo("郭大侠","961012",21);
out.writeObject(userInfo);//序列化一个会员对象
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从文件中反序列化对象
* @param fileName
*/
public static void deserialize(String fileName){
try {
ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));
String str=(String) in.readObject();//刚才的字符串对象
Date date=(Date) in.readObject();//日期对象
UserInfo userInfo=(UserInfo) in.readObject();//会员对象
System.out.println(str);
System.out.println(date);
System.out.println(userInfo);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
// serialize("text");
deserialize("text");
/**
* 我修改了一下UserInfo的无参构造,在无参构造中给userAge属性赋值蛋反序列化获得的结果是userAge变成了20。
* 得出结论:
* 当从磁盘中读出某个类的实例时,若是该实例使用的是Externalizable序列化,会执行这个类的构造函数,
* 而后调用readExternal给其余属性赋值
*/
}
}
原理分析:
总结:
首先,咱们在序列化UserInfo对象的时候,因为这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性能够序列化,哪些不能够序列化,因此,对象在通过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,而后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,而后在测试类接收,就完成了反序列
一些api:
Externalizable 实例类的惟一特性是能够被写入序列化流中,该类负责保存和恢复实例内容。 若某个要彻底控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替定制的 writeObject 和 readObject 方法实现。
writeExternal(ObjectOutput out)
该对象可实现 writeExternal 方法来保存其内容,它能够经过调用 DataOutput 的方法来保存其基本值,或调用 ObjectOutput 的 writeObject 方法来保存对象、字符串和数组。
readExternal(ObjectInput in)
对象实现 readExternal 方法来恢复其内容,它经过调用 DataInput 的方法来恢复其基础类型,调用 readObject 来恢复对象、字符串和数组。
externalizable和Serializable的区别:(静态属性持保留意见,60%偏向不能直接序列化)
1:
实现serializable接口是默认序列化全部属性,若是有不须要序列化的属性使用transient修饰。
externalizable接口是serializable的子类,实现这个接口须要重写writeExternal和readExternal方法,指定对象序列化的属性和从序列化文件中读取对象属性的行为。
2:
实现serializable接口的对象序列化文件进行反序列化不走构造方法,载入的是该类对象的一个持久化状态,再将这个状态赋值给该类的另外一个变量
实现externalizable接口的对象序列化文件进行反序列化先走构造方法获得控对象,而后调用readExternal方法读取序列化文件中的内容给对应的属性赋值。
serialVersionUID做用:序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的惟一性。 有两种生成方式: 一个是默认的1L,好比:private static final long se...
serialVersionUID做用:
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的惟一性。
有两种生成方式:
一个是默认的1L,好比:private static final long serialVersionUID = 1L;
一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段;
几个问题:
一、 若是一个类没有实现Serializable接口,可是它的基类实现 了,这个类可不能够序列化?
二、 和上面相反,若是一个类实现了Serializable接口,可是它的父类没有实现 ,这个类可不能够序列化?
第1个问题:一个类实现 了某接口,那么它的全部子类都间接实现了此接口,因此它能够被 序列化。
第2个问题:Object是每一个类的超类,可是它没有实现 Serializable接口,可是咱们照样在序列化对象,因此说明一个类要序列化,它的父类不必定要实现Serializable接口。可是在父类中定义 的状态能被正确 的保存以及读取吗?
第3个问题:若是将一个对象写入某文件(好比是a),那么以后对这个对象进行一些修改,而后把修改的对象再写入文件a,那么文件a中会包含该对象的两个 版本吗?
关于几个问题的答案见这个博客:http://blog.csdn.net/moreevan/article/details/6698529(我是从上面copy转过来的,不肯深刻了)