序列化与反序列化

文章来源于Hollis干货集散地

//新建实体类实现序列化接口
public class People implements Serializable {

    private static final long serialVersionUID = 4547806378767721442L;

    private String name;

    private Integer age;

}
public static void main(String[] args) throws Exception {
	//通过下面代码对People类进行序列化与反序列化
	 People people = new People();
       people.setName("张三");
       people.setAge(23);
       System.out.println(people);
	//写入文件
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(people);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            oos.close();
        }
        //读出文件
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            People newUser = (People) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            File file1 = new File("tempFile");
            file1.deleteOnExit();
            ois.close();
        }

    }
}
	//打印
	People(name=张三, age=23)
	People(name=张三, age=23)

对于这段代码大家都很熟悉,新建实体类并实现序列化接口,那么什么是序列化呢?

1.序列化与反序列化

序列化:是将对象转换为可传输格式的过程,一般以字节码或XML格式传输,是一种数据的持久化手段,一般广泛应用于网络传输,RMI和RPC场景中。
反序列化:字节码或XML编码格式可以还原为完全相等的对象,这个相反的过程称为反序列化。

2.如何实现序列化与反序列化

Java提供了一套java对象序列化及反序列化的API,其中包括如下接口和类:java.io.Serializablejava.io.ExternalizableObjectOutputObjectInputObjectOutputStreamObjectInputStream.

Serializable 接口

类通过实现Serializable 接口实现其序列化功能,可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。若没实现该接口,则报java.io.NotSerializableException

Externalizable接口

Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器

ObjectOutput和ObjectInput 接口

ObjectInput 扩展自 DataInput 接口以包含对象的读操作DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾 (end of file),则将抛出 EOFException(IOException 的一种)。如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出 IOException 而不是 EOFException。尤其是,在输入流已关闭的情况下,将抛出 IOException。
ObjectOutput 扩展 DataOutput 接口以包含对象的写入操作。DataOutput 接口用于将数据从任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出 IOException。

ObjectOutputStream类和ObjectInputStream类

通过前面的代码片段中我们也能知道,我们一般使用ObjectOutputStreamwriteObject方法把一个对象进行持久化。再使用ObjectInputStreamreadObject从持久化存储中把对象读取出来

3.如何让某些成员变量不被序列化?

可以在成员变量前用transient关键字修饰,被修改的变量则不被序列化,然后在被反序列化后,transient变量的值被设为初始值,如int为0,对象型为null

4.serialVersionUID 有什么作用?

虚拟机是否允许反序列化取决于serialVersionUID 是否一致,否则就会出现序列化版本不一致的异常,即是InvalidCastException,这样做是为了保证安全,因为文件存储中的内容可能被篡改。如果没有显示的定义serialVersionUID ,则会根据编译的class自动生成一个serialVersionUID 做比较

5.序列化是如何破坏单例模式的?

单例类

//使用双重校验锁方式实现单例
public class Singleton implements Serializable {
    private volatile static Singleton singleton;
    private Singleton(){

    }
    public Singleton getSingleton(){
        if (singleton == null) {
            synchronized (Singleton.class){
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
       return singleton;
    }
}

测试类

   public static void main(String[] args) throws Exception {
	//序列化与反序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(Singleton.getSingleton());
        oos.close();
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        //判断是否是同一个对象
        System.out.println(newInstance == Singleton.getSingleton());
        ois.close();
    }
	//打印
	false 说明通过序列化与反序列化生成了一个新对象,破坏了Singleton的单利性

为什么会产生这样的后果?

ObjectInputStream

对象的序列化过程通过ObjectOutputStreamObjectInputputStream来实现的,ObjectInputStream的readObject的调用栈为:readObject—>readObject0—>readOrdinaryObject—>checkResolve
readOrdinaryObject
方法片段(省略部分代码)

Object obj;  //为此方法的返回对象
        try {
        //isInstantiable:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true
        //desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象
            obj = desc.isInstantiable() ? desc.newInstance() : null; 							
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

此时可看到序列化破坏单例模式的原因为:序列化时通过无参构造新建一个对象

如何防止序列化破坏单例模式

在Singleton 类中定义readResolve就可以解决该问题:

public class Singleton implements Serializable{
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    private Object readResolve() {
        return getSingleton();
    }
}

原因

readOrdinaryObject方法中有另一个代码片段:

if (obj != null &&
            handles.lookupException(passHandle) == null &&
         //hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true
            desc.hasReadResolveMethod())
        {
        //invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

这样就可以防止序列化对单利的破坏了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值