对象的序列化与反序列化
对象序列化就是将Object对象转换成Byte序列,反之则为反序列化。
序列化流(ObjectOutputStream)即过滤流 —>writeObject
反序列化流,ObjectInpuStream —-> readObject
对象首先必须实现序列化接口,才能进行序列化。
注意点: 这个接口没有任何方法
eg:
public class Main {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
String file = "demo/raf.dat";
// 对象序列化,存入二进制文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
Student s1 = new Student("1001","张三",20);
oos.writeObject(s1);
oos.flush();
oos.close();
// 对象反序列化,读到对象中
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
try {
Student stu = (Student)ois.readObject();
System.out.println(stu);
ois.close();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
transient
该关键字能使java对象里的属性不会被jvm默认的关键字序列化
这仅仅意味着不会被jvm序列化,但可以按照自己的序列化来进行序列化
如Student类中
private String No;
private String name;
private transient int age;
通过自己拟定方法签名来进行序列化
// 序列化 方法签名
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
s.defaultWriteObject(); //虚拟机能默认序列化的元素进行序列化操作
s.writeInt(age); // 自己完成序列化
}
// 反序列化 方法签名
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException{
try {
s.defaultReadObject();
this.age = s.readInt();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
总结: transient关键字可以提高性能
Arraylist源码分析
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
在Arraylist中,将存储元素的数组添加了transient关键字,其意义并不在于不让数组元素序列化,而是将其自身实现了方法签名来序列化,以此提高性能,在Arraylist的方法签名中,只将有效元素进行序列化,而个数则是由size得到,从而提高了性能
序列化中父类与子类的关系
- 一个类实现了序列化接口,其子类都可以进行序列化
对于类与其子类Foo类
public class Foo implements Serializable {
public Foo (){
System.out.println("foo");
}
}
class Foo1 extends Foo{
public Foo1(){
System.out.println("Foo1");
}
}
class Foo2 extends Foo1{
public Foo2(){
System.out.println("Foo2");
}
}
我们将其进行序列化与反序列化
ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("demo/raf.dat"));
Foo2 foo2 = new Foo2();
oos.writeObject(foo2);
oos.flush();
oos.close();
// 此时递归调用父类的构造函数
// foo
// Foo1
// Foo2
ObjectInputStream ois = new ObjectInputStream (new FileInputStream("demo/raf.dat"));
try {
Foo2 foo2 = (Foo2)ois.readObject();
System.out.println(foo2);
ois.close();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
反序列化的控制台结果:
Serializable.Foo2@3d4eac69
可以看出,序列化时,递归调用了构造函数,反序列化时,没有显示出打印语句,但是还不能断言没有构造函数。
再构造一个Bar类及其子类,其中Bar类没有实现序列化接口,而其子类实现了
package Serializable;
import java.io.Serializable;
public class Bar {
public Bar(){
System.out.println("bar");
}
}
class Bar1 extends Bar implements Serializable{
public Bar1(){
System.out.println("Bar1");
}
}
class Bar2 extends Bar1 {
public Bar2(){
System.out.println("Bar2");
}
}
然后我们再一次将主函数里面将Bar2类序列化与反序列化时,反序列化时控制台打印的信息为:
bar
Serializable.Bar2@55f96302
这里可以看出,Bar类的构造函数被显式的调用了
结论:
对子类对象进行反序列化时,如果其父类没有实现了序列化接口,其构造函数将被显式的调用