Java中的序列化

1.什么是序列化?

将数据对象转换为二进制流的过程称为序列化(Serialization),反之将二进制流恢复为数据对象的过程称为反序列化(DEserialization)。

内存中的数据对象只有转换为二进制流才可以进行数据持久化和网络传输。序列化需要保留充分的信息以恢复数据对象,但是为了节约存储空间和网络宽带,序列化后的二进制流又要尽可能小。序列化常见的使用场景是RPC框架的数据传输。

常见的序列化有以下三种:

  • Java序列化:不支持跨语言,兼容性较差
  • Hessian序列化:支持跨语言
  • Json序列化

2.Java中实现序列化

在Java中我们可以通过对象流ObjectOutputStreamObjectInputStream结合Serializable或者Externalizable实现序列化和反序列化。

2.1.Serializable接口

实体类:

public class User1 implements Serializable {

    private static final long serialVersionUID = 2021097002352557981L;

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

通过对象流实现序列化和反序列化:

public class SerializableDemo1 {
    public static void main(String[] args) {
        User2 user = new User2();
        user.setName("pikachues");
        user.setAge(18);
        System.out.println(user);

        // 序列化
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile1.txt"));
            oos.writeObject(user);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("tempFile1.txt"));
            User2 readUser = (User2)ois.readObject();
            System.out.println(readUser);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(ois!=null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出:

User{name='pikachues', age=18}
User{name='pikachues', age=18}

总结几个点:

  • 通过实现Serializable为开启序列化

  • serialVersionUID:如果未设置编译器会根据类名、接口名、属性名等来动态生成,源码修改serialVersionUID会发生变化,因此需要设置确保兼容性。

  • 当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出 NotSerializableException

  • 如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成java.io.Serializable接口。

  • 如果我们不想让某个属性实例化,可以加transient修饰,或者用static修饰。

2.2.Externalizable接口

实现Serializable接口是默认将所有属性实例化,如果我们要定制实例化,除了用transient修饰变量不序列化,我们还可以通过实现Externalizable来定制序列化。

实体类:

public class User2 implements Externalizable {

    private static final long serialVersionUID = -2807033361161051869L;
    private String name;
    private String pass;
    private int age;

// 省略set get
    
    @Override
    public String toString() {
        return "User2{" +
                "name='" + name + '\'' +
                ", pass='" + pass + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeObject(pass);
        out.writeInt(age);

    }

    /**
     * 调用被序列化类的无参构造器去创建一个新的对象
     * @param in
     * @throws IOException
     * @throws ClassNotFoundException
     */
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // 读写顺序保持一致
        name = (String)in.readObject();
        pass = (String)in.readObject();
        age = in.readInt();

    }
}

通过对象流实现序列化和反序列化:

public class ExternalizableDemo1 {
    public static void main(String[] args) {
        User2 user = new User2();
        user.setName("pikachues");
        user.setPass("123");
        user.setAge(18);
        System.out.println(user);

        // 序列化
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile2.txt"));
            user.writeExternal(oos);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("tempFile2.txt"));
            User2 readUser = new User2();
            readUser.readExternal(ois);
            System.out.println(readUser);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(ois!=null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

Externalizable继承了Serializable,该接口中定义了两个抽象方法:

  • writeExternal():实现序列化细节
  • readExternal():实现反序列化细节
  • 这两个方法中读写顺序应该保持一致。

3.Java中防止序列化破坏单例模式

实体类:

public class Singleton implements Serializable {
    private static final long serialVersionUID = 3809856658206678778L;

    private static volatile Singleton singleton;

    public Singleton() {
    }

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

    private Object readResolve() {
        return singleton;
    }
}

序列化反序列化测试:

public class SerializableDemo1 {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile3.txt"));
            oos.writeObject(Singleton.getSingleton());
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("tempFile3.txt"));
           Singleton newSingleton = (Singleton)ois.readObject();
            System.out.println(newSingleton==Singleton.getSingleton());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(ois!=null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

结果为false,这里可以看出序列化会破坏单例模式,要想防止序列化破坏反射只需要在对应的序列化实体类中添加readResolve方法

public class Singleton implements Serializable {
    private static final long serialVersionUID = 3809856658206678778L;

    private static volatile Singleton singleton;

    public Singleton() {
    }

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

    private Object readResolve() {
        return singleton;
    }
}

这时候再对以上序列化反序列化进行测试结果为true。

4.总结

  • 序列化是将数据对象转换成二进制数据,反序列化是将二进制数据转换成对象
  • Java中通过类实现Serializable开启序列化
  • Java中序列化会破坏单例模式,可以在实现类中添加readResolve方法。

参考:

  • 《码出高效》

  • 《Java成神之路》

  • https://www.hollischuang.com/archives/1144

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值