定义:
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.youkuaiyun.com/wangloveall/article/details/7992448/