序列化与反序列化

序列化基础:
即使用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,则其反序列化的值对于int0,引用则为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();
    }
    //这里要是static,否则调用defaultWriteObject时,会将类信息写入,导致读异常。
    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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值