序列化与反序列化

序列化基础:
即使用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);
    }
    }
    }

转载于:https://www.cnblogs.com/fcat/p/5239393.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值