序列化与反序列化

来源:互联网 发布:php服务器程序招聘 编辑:程序博客网 时间:2024/05/21 11:04
序列化基础:即使用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);        }    }}
0 0
原创粉丝点击