关于序列化和反序列化的笔记
程序运行时,对象都位于内存中(确切地说是堆内存中),程序结束,对象的结束。能否把对象保存起来呢?这就可以使用序列化:
(1)将对象的字节码永久地保存到硬盘的文件中;
(2)在网络上传送对象的字节码序列。
如图所示:
前提:
只有实现了java.io.Serializable 接口或者java.io.Externalizable接口(后者是前者的子接口) 的类的对象才能序列化。实现了这两个接口之一的类是可序列化的,可序列化类的所有子类也都是可序列化的。注意java.io.Serializable接口只是标记接口,并没有方法,与Clonable接口类似。JDK中很多类实现了Serializable接口,如String、8种包装类、Date。
java.io.ObjectOutputStream和java.io.ObjectInputStream是序列化的主要类,它们分别有writeObject(Object o)和readObject()方法,分别表示将对象字节序列o写入目标输出流(也就是序列化)和从源输入流读入对象字节序列(再将其反序列化成一个对象,然后返回)。
以下是它们的构造方法:
public ObjectOutputStream(OutputStream out)
public ObjectInputStream(InputStream in)
实现Serializable接口的类是采用默认的方式序列化对象,而实现Externalizable接口的类可以自己控制序列化行为。也就是说:
(1)如果一个类仅仅实现了Serializable接口,那么,ObjectOutputStream按默认的方式将对象序列化,就是说将非transient实例变量进行序列化。ObjectInputStream将非transient实例变量进行反序列化。
(2)如果一个类实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out),那么ObjectInputStream和ObjectOutputStream就分别调用自定义的readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out),方法进行序列化和反序列化。
(3)如果一个类实现了Externalizable接口,那么此类必须实现void readExternal(ObjectInputin)
方法和void writeExternal(ObjectOutputout)
方法。注意,readExternal(ObjectInputin)
方法与ObjectInputStream(InputStreamin)的readObject()方法的签名不同。ObjectOutputStream调用此类的readExternal(ObjectInputin)方法进行序列化;ObjectInputStream先通过此类的构造方法(不带参数)构造一个对象,然后调用它的
readExternal(ObjectInputin)进行反序列化。
对于第一种情况,序列化步骤:
1、创建对象输出流(常包装其他输出流,如文件输出流FileOutputStream,这从构造方法中就可以看出来):
FileOutputStream fos = newFileOutputStream(文件路径);
ObjectOutputStreamoos = new ObjectOutputStream(fos);
或者在网络上传输
ObjectOutputStreamoos = new ObjectOutputStream(socket.getOutputStream());
2、通过writeObject(Object o)写对象:
oos.wrtiteObject("测试文本");等等。
反序列化步骤:
1、与上面类似,创建对象输如流(常包装其他输出流,如文件输出流FileIntputStream):
FileOutputStream fis = newFileInputStream(文件路径);
ObjectInputStreamois = new ObjectOutputStream(fis);
或者在网络上传输
ObjectInputStreamois = new ObjectInputStream(socket.getInputStream());
2、读对象
ois.readObject();
注意:反序列化不会调用此类的构造方法获得对象。读取顺序必须与写入的顺序一致,才能得到正确的对象。另外,写入的对象和读入的对象不是同一个对象(即用==比较不相等)。但是,如果同一个对象写入两次,那么读入的这两个对象是同一个对象。
对于默认的序列化,仅仅序列化非transient的实例变量,静态变量、transient变量不会序列化,客户端也就得不到这些数据。反序列化是,会先加载和初始化被序列化的对象的类,但是不会调用此类的构造方法。
我们知道,一个类实现了Serializable 接口,那么其子类也是可以序列化的。而且,如果A类对象可序列化,并且它还持有B对象(也是可序列化的)的引用,那么序列化A时,也会序列化B。如果A类实现过程依赖了大量的看序列化对象,那么序列化A是,将会消耗大量资源。
对于第二种情况,我们通常是在默认序列化和反序列化的基础上加上自己的控制方式。我们定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out),那么ObjectInputStream和ObjectOutputStream就分别调用自定义的readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out),方法进行序列化和反序列化。序列化时,我们可以在writeObject(ObjectOutputStream out)中按默认的方式序列化,调用的ObjectOutputStream的defaultWriteObject(Object o)方法,然后可以增加自己的写数据的方法;类似的,反序列化时,我们可以在readObject(ObjectInputStream out)中按默认的方式反序列化,调用的ObjectInputStream的defaultReadObject()方法,然后可以增加自己相应的读数据的方法。这种方式,我们可以传递重要的数据,也能够对数据的正确性做一定的检查。
对于第三种情况,类实现了Externalizable接口,writeExternal()负责序列化,readExternal()负责反序列化。而进行反序列化时,ObjectInputStream先通过此类的构造方法(不带参数)构造一个对象,然后调用它的readExternal(ObjectInputin)进行反序列化(也会加载和初始化这个对象的类)。所以,这个对象所在的类(以及它的对象引用的对象的类)必须拥有public级别的不带参数的构造方法,否则抛出异常。