序列化基础:
即使用ObjectOutputStream与ObjectInputStream进行对象与字节流的转换,一般需要提供一个序列化id。
tip:默认序列化时若一个域被修饰为transient,则不序列化该实例域。
import java.io.*;
public class Test {
public static void main(String[] args) throws Exception{
//将两个对象序列化存储到文件中
File f = new File("oos.txt");
System.out.println(f.exists());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
oos.writeObject(new T(1));
oos.writeObject(new T(2));
oos.close();
//从序列化文件反序列化生成两个对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
T t1 = (T)ois.readObject();
T t2 = (T)ois.readObject();
t1.get();
t2.get();
ois.close();
}
}
class T implements Serializable {
private int x;
public T(int x){
this.x = x;
}
public void get(){
System.out.println(x);
}
/**
* Serilizable接口没有抽象方法,所以以下四个方法可以不写
以下四个方法,为自定义序列化时的可选方法,将由ObjectOutputStream
与ObjectInputStream进行反射调用。
*/
//此方法在写入序列化文件时最先被调用,其返回一个Serilizable对象用于代替当前对象进行序列化
private Object writeReplace(){
return new T(5);
}
//此方法用于选择保存当前对象的关键域(决定这个对象的实例域)到序列化文件
private void writeObject(ObjectOutputStream os) throws Exception {
//为了往后兼容
os.defaultWriteObject();
os.writeInt(x);
}
//此方法用于从序列化文件中获取数据用来恢复关键域
private void readObject(ObjectInputStream is) throws Exception{
//为了往后兼容
is.defaultReadObject();
x = is.readInt();
}
//此方法在恢复对象时最后被调用,其返回一个对象用于替代文件恢复的对象,一般用于序列化代理
private Object readResolve(){
return new T(4);
}
}
序列化高级:
谨慎地实现Serilizable接口,其代价如下
一旦类被公布,就降低了修改这个类的可能性
增加了bug和可能问题,可能破坏singleton模式
测试负担增加
考虑自定义的序列化形式
考虑以下的StringList类,若使用默认的自定义形式,其将对head进行序列化,因此对链表的每个节点进行序列化,一来,增大了序列化的大小;二来,使得字符串列表限制只能使用链表Entry实现;三来,增大
了序列化时间,其将对previous与next均进行序列化,需要有昂贵的图遍历过程,而我们可以简单调用next获得字符串列表;四来,在元素多时,递归序列化可能造成栈溢出。
import java.io.Serializable;
/**
- Created by Doggy on 2015/9/13.
*/
public final class StringList implements Serializable{
private int size = 0;
private Entry head = null;
private static class Entry implements Serializable{
private Entry previous;
private Entry next;
private String value;
}
}
因为对于字符串列表来说只关心字符串个数与顺序,所以可以采用以下自定义的序列化方法代替,
自定义序列化时大部分实例域应该被标记为transient(一个域被声明为transient,则其反序列化的值对于int为0,引用则为null,直到执行readObject才会初始化)
编写一个线程安全的可序列化类需要对readObject以及writeObject加锁
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/** - Created by Doggy on 2015/9/13.
*/
public final class StringList implements Serializable{
//修饰为transient避免默认序列化时序列化该实例域
private transient int size = 0;
private transient Entry head = null;
//添加一个增加字符串的方法
private final void addOne(String s){
Entry ent = new Entry();
ent.value = s;
Entry tmp = head;
while(tmp.next != null){
tmp = tmp.next;
}
ent.previous = tmp;
tmp.next = ent;
}
private static class Entry implements Serializable{
private Entry previous;
private Entry next;
private String value;
}
//编写writeObject进行自定义序列化
private void writeObject(ObjectOutputStream os) throws Exception{
//为了向后拓展,后期在类中加入一些实例域可能有用
os.defaultWriteObject();
//写入字符串列表的大小
os.writeInt(size);
//将字符串列表中的每个字符串按顺序写入文件
while(tmp.next != null){
os.writeObject(tmp.value);
tmp = tmp.next;
}
}
private void readObject(ObjectInputStream is) throws Exception{
//为了向后拓展,后期在类中加入一些实例域可能有用
is.defaultReadObject();
//读取大小到对象中
size = is.readInt();
//根据列表元素以及addOne方法进行恢复
for (int i = 0; i < size; i++) {
addOne((String)is.readObject());
}
}
}
保护性地编写readObject方法
/
readObject应该与构造器类似,不能(间接)调用一个可覆盖的方法
* 且应该实现与构造器一致的有效性检测与保护性拷贝(防止内部实例域引用泄露)
*/
private void readObject(ObjectInputStream is) throws Exception{
//为了向后拓展,后期在类中加入一些实例域可能有用
is.defaultReadObject();
//保护性拷贝,若不实现,则可能通过伪造字节流,获得对start与end的引用,在客户端修改该类的start、end域,影响类的不可变性
start = new Date(start.getTime());
end = new Date(end.getTime());
//数据有效性检测
if(start.compareTo(end) > 0){
throw new InvalidObjectException();
}
}
枚举单例优先于使用readResolve控制的序列化单例
//可以在readResolve中直接返回单例对象,但所有实例域必须被声明为transient
//否则在未执行readResolve之前的readObject产生的新单例对象可能被盗用。
private Object readResolve(){
return INSTANCE;
}
考虑使用序列化代理代替序列化实例
/**
- 好处是外部类的所有实例都是从构造器创建,
- 所以可以防止以上的伪造流以及盗用者造成的危害
- 也不用特别检测数据的有效性,因为在构造器中已经检测过
*/
class Period{
private final Date start;
private final Date end;
public Period(Date start,Date end){
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
//数据有效性检测
if(start.compareTo(end) > 0){
throw new InvalidParameterException();
}
}
//使用writeReplace将序列化任务转发给代理,所以不存在任何外部类的序列化实例
private Object writeReplace(){
return new PeriodProxy(start,end);
}
//防止对外部类使用字节流创建对象,直接对伪造流抛异常
private void readObject(){
throw new InvalidObjectException();
}
private static class PeriodProxy{
private final Date start;
private final Date end;
private PeriodProxy(Date start,Date end){
this.start = start;
this.end = end;
}
//自定义实现readObject和writeObject
// ...
//实现writeResolve,将内部代理转换为外部类对象
private Object readResolve(){
//若为单例则直接返回INSTANCE
return new Period(start,end);
}
}
}