Java对象的序列化机制
对象的序列化,是将内存中的java对象转化为二进制的字节流,然后保存到磁盘中或者在网络上。这就是序列化对象,反序列化顾名思义就是将对象的二进制字节流恢复成原来的对象。注意只有对象的类名和属性能被序列化(包括基本类型,数组,对其他对象的引用)不包括方法,static属性(静态属性),transient属性(瞬态属性)都不会被序列化。
Java中不是所有类都是序列化类,如果一个类要实现序列化就必须实现下面两个接口之一:
接口 (2)Externalizable接口
对于这两个接口的区别后面就会知道的,我们先把类都实现Serializable接口,这个接口java没有提供任何的方法,java设计他只是作为一个类的序列化的标志,表明此类可以进行序列化。
java对象转化为二进制的字节流并写出,就一定需要对象的流来进行输出,所以这里用到ObjectOutputStream类,这个输出流是一个处理流,所以需要创建一个字节输出流然后用这个处理流进行包装,所以处理流也叫作包装流。反之ObjectInputSteam是将对象写入的类。写出的对象方法是ObjectOutputStem对象.writeObject(序列化对象的实例)。写入的方法是ObjectInputStream的对象.readObject(序列化的对象);说了这么多,我就做一个例子吧。。
Person类
public class Person implements Externalizable {
private String name;
private int age;
public Person1(String name,int age) {
this.name=name;
this.age=age;
//这个地方用于测试序列化和反序列化对象时候实例化类的情况
System.out.println("带参数的构造器的使用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
代码:创建测试类,进行测试
public static void main(String[] args) throws IOException {
//如果处理流,当关闭流的时候就不需要再将节点流进行关闭了
//这是对对象进行写出的操作(序列化对象)
FileOutputStream fos=null;
ObjectOutputStream oos=null;
try {
fos=new FileOutputStream(new File("E://javatest.txt"));
oos=new ObjectOutputStream(fos);
Person per=new Person("孙悟空",66);
oos.writeObject(per);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(oos!=null) {
oos.close();
}
}
//这是对对象进行写入的操作(反序列化对象)
FileInputStream fis=null;
ObjectInputStream ois=null;
try {
fis=new FileInputStream("E://javatest.txt");
ois=new ObjectInputStream(fis);//处理流将文件的字节流进行包装
//将对象反序列化写入的时候得到的都是Object类型的数据,必须进行强制类型的转化
Person per=(Person)ois.readObject();
System.out.println("姓名"+per.getName()+"年龄"+per.getAge());
} catch (Exception e) {
e.printStackTrace();
} finally {
if(ois!=null) {
ois.close();
}
}
}
readObject方法会抛出ClassNotFoundException异常,也就是说当反序列化时候找不到对应的java类会将引发这个异常,因为反序列化读取的仅仅是java对象的数据,而不是java类,因此采用反序列化恢复java对象时,必须提供该java对象所属类的class文件,否则就会引发该异常。
输出结果可以看出当反序列化的时候构造器没有执行,也就是反序列化类无需通过构造器来进行初始化java对象
注:如果我们向文件中使用序列化机制写入多个java的对象,使用反序列化机制恢复对象时必须按实际的写入顺序读取。
属性是引用类型的对象的序列化
我们上面所说的属性都是String类型和基本类型,如果我们需要一个引用类型呢,那么这个引用类型也必须是可序列化的类,否则拥有该类型的属性类不可序列化。下面我将要定义一个引用类型的属性,重新创建一个Teacher类,引用属性必须实现序列化,否则Treacher不论实现不实现(1)Serializable接口 (2)Externalizable接口这两个接口,他都不是序列化的类,因为当对象序列化的时候,会顺带着把引用类型的属性进行序列化,所以要想Teacher是序列化的类,则必须将Person的类进行序列化。
Teacher类
public class Teacher implements Serializable {
private String name;
private Person student;//引用类型的属性(这个Person类就是上面实现序列化的类)
public Teacher(String name,Person student) {
this.name=name;
this.student=student;
System.out.println("带参数的构造器的使用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getStudent() {
return student;
}
public void setStudent(Person student) {
this.student = student;
}
}
代码 测试代码
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
FileOutputStream fos=null;
ObjectOutputStream oos=null;
Person p=new Person("孙悟空",66);
Teacher t1=new Teacher("玄奘法师",p);
Teacher t2=new Teacher("菩提祖师",p);
try {
fos=new FileOutputStream(new File("E://javatext.txt"));
oos=new ObjectOutputStream(fos);
oos.writeObject(t1);
oos.writeObject(t2);
oos.writeObject(p);
oos.writeObject(t1);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(oos!=null) {
oos.close();
}
}
}
}
分析这段代码可以看出我创建出来了三个类。Person p=new Person("孙悟空",66);Teacher t1=new Teacher("玄奘法师",p);Teacher t2=new Teacher("菩提祖师",p);如果我将这三个类进行对象的序列化的话,t1写出并且Person类也会进行序列化,同理t2也是,然后我们又显示序列化了Person类,所以Person类在此次写出中,被序列化了三回,那么对于t1,t2来说实际上他们的Person类是同一个,但是如果Person序列化三回的话,t1,t2就没有引用同一个Person类,这显然是不符合实际情况的。Java对此采用了一种特殊的序列化算法,算法的内容是:
(1)所有保存到磁盘中的对象都有一个序列化编号。
(2)当程序师徒序列化一个对象的时候,程序将先检查对象是否已经序列化过,只有当该对象从未(在本次虚拟机中)被序列化过,系统才会将该对象转化成字节序列并输出。
(3)如果某个对象是已经序列化过的,程序将直接只是输出一个序列化编号,而不是重新序列化该对象。
Y(^o^)Y序列化的对象是可变的类
根据java的序列化机制,当我先写进去序列化的时候,如果我改变了可变类的属性值,那么当我想再次进行序列化的时候就不能把更改后的值序列化了,因为java的序列化机制当在此序列化同一的对象的时候,输出的是一个序列化编号。程序会比较两个对象是同一个对象,就不会把对象重新的序列化。就是更改后的对象并没有被写入。这再次验证了java的序列化机制。
代码 测试类
public class VolatileClassTest {
/**
* 序列化可变类
* @param args
* @throws IOException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
FileOutputStream fos=null;
ObjectOutputStream oos=null;
FileInputStream fis=null;
ObjectInputStream ois=null;
try {
//进行序列化的操作
fos=new FileOutputStream(new File("E://javatest.txt"));
oos=new ObjectOutputStream(fos);
Person p=new Person("孙悟空",600);
oos.writeObject(p);//将对象进行序列化
p.setName("红孩儿");//可变类将姓名属性设置为红孩儿,可变类
oos.writeObject(p);//将更改后的类进行序列化
//进行反序列化操作
fis=new FileInputStream(new File("E://javatest.txt"));
ois=new ObjectInputStream(fis);
Person per=(Person)ois.readObject();
//输出的姓名还是孙悟空,再次验证了java的序列化机制
System.out.println("姓名是"+per.getName());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(oos!=null) {
oos.close();
}
if(ois!=null) {
ois.close();
}
}
}
}