【Java-EE初阶】序列化和反序列化

1.为什么使用序列化?

在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。创建出来的这些对象都是存在于Jvm的堆内存中的。只有Jvm处于运行状态的时候,这些对象才可能存在,一旦Jvm停止下来,这些对象的状态也就随之丢失了。但是在真是的应用场景中,我们需要将这些对象持久化起来,并且能够在需要的时候把对象重新读取出来。java的对象序列化可以帮助我们实现该功能。序列化就是将对象信息存储来文件中,减少了内存资源使用

通过序列化可以将对象转化成一个字节数组流,并且有需要的时候将字节数组流转化成一个对象

在Java中,对象的序列化与反序列化的被广泛应用到RMI(远程方法调用)及网络传输

2.如何实现序列化和反序列化呢?

如果想要每个对象支持序列化机制,必须让它的类是可序列化的,则该类必须实现以下两个接口之一Serializable,Externalizable。

3.常见的序列化方案有哪些?

方案1、基于Java原生的ObjectOutputStream.write()和ObjectInputStream.read()来进行对象序列化和反序列 化。

方案2、基于JSON进行序列化和反序列化。

方案3、基于XML进行序列化和反序列化。

我们这次着重来讲java原生的ObjectXXStream的序列化操作。

4.序列化接口

1) Serializable 接口

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化。可序列化类的所有子类本身都是可序列化的。序列化接口没有方法或字段,仅仅是可序列化的语义。

@Data
public class UserInfo implements Serializable {
    private String name;
    private int age;
}

public static void main(String[] args) throws IOException {
        UserInfo user = new UserInfo();
        user.setAge(20);
        user.setName("zhangsan");

        //将序列化写入到文件中去
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("test.txt"))) {
            oos.writeObject(user);
        }catch (IOException e) {
            System.out.println(e.getMessage());
        }

        try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream("test.txt"))) {
           UserInfo userInfo =(UserInfo) ois.readObject();
            System.out.println(userInfo);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

其实在序列化持久化存储的时候一般都是将这个对象的信息存储在一个文件中,这样对象的信息都不会进行丢失。

Stream的作用进行数据的转换、筛选、聚合等操作,可以极大地简化对数据的处理。使用Stream可以避免显式地使用迭代器或循环来操作集合,提高代码的可读性和简洁性。

以上是将对象写入到文件中去,然后我们也可以将文件读取转换成一个对象。

在这里我们可能很眼熟,在前后端分离开发的过程中,将数据返回到前端的时候,我们一般将数据转化成一个json格式的数据返回给前端。同时也有可能会将数据进行封装,统一格式的返回。

2) Externalizable 接口

Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用 Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。

@Data
public class UserInfo1 implements Externalizable {
    private String name;
    private int age;

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {

    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    }
}



 public static void main(String[] args) throws IOException {
        UserInfo1 user = new UserInfo1();
        user.setAge(20);
        user.setName("zhangsan");

        //将序列化写入到文件中去
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("test1.txt"))) {
            oos.writeObject(user);
        }catch (IOException e) {
            System.out.println(e.getMessage());
        }

        try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream("test1.txt"))) {
            UserInfo1 userInfo1 =(UserInfo1) ois.readObject();
            System.out.println(userInfo1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

以上代码输出可以看出,对UserInfo1类进行序列化的时候,得到的对象的所有属性的值都变成了默认值,也就是说之前那个对象的状态并没有被持久化下来

是因为使用Externalizable进行序列化的时候,在读取对象的时候,会调用被序列化的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中,所以实现这个接口的时候提供一个public的无参构造器。在上面我使用了一个@Data注解,就提供了一个无参构造器。

但是我们提供一个无参的构造器,输出的结果还不是我们想要的。

所以说得重写这个接口的方法。

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

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       name=(String) in.readObject();
       age = in.readInt();
    }

序列化ID与Transient关键字

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致(就是private static final long serialVersionUID)。序列化ID提供了两种生成策略,一个是固定的1L,另一个是随机生成一个不重的long类型的数据(实际上是使用Jdk工具生成)。

首先我们先明白反序列化的过程

当反序列化时,Jvm会读取序列化后的字节流中的类信息 。

Jvm会尝试根绝类路径查找对应的类定义,如果没有找到,就会抛出ClassNotFoundException异常。

假如,序列化时类处于com.example.Myclass,而反序列化时类处于com.example.subpackage.Myclass,则类路径不一致,反序列化失败。

 import lombok.Data;
    @Data
    public class User1 implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private int age;
        @Override
        public String toString() {
            return "User1{" + "name=" + name + ", age=" + age + "}";
        }
    }


 import lombok.Data;
    @Data
    public class User4 implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private int age;
        @Override
        public String toString() {
            return "User4{" + "name=" + name + ", age=" + age + "}";
        }
    }

    import lombok.Data;
    @Data
    public class User5 implements Serializable {
        private static final long serialVersionUID = 2L;
        private String name;
        private int age;
        @Override
        public String toString() {
            return "User5{" + "name=" + name + ", age=" + age + "}";
        }
    }
 User1 user = new User1();
    user.setName("mike");
    user.setAge(22);
    System.out.println(user);
// 将对象序列化后写入文件
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"))) {
            oos.writeObject(user);
        } catch (IOException e) {
    // do some exception operation
        }
    // 从文件中读取对象信息并反序列化为对象
    File file = new File("tempFile");
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
        User4 newUser1 = (User4) ois.readObject(); // 序列化ID一致,序列化成功
// User5 newUser2 = (User5) ois.readObject(); // 序列化ID不一致,序列化异常
        java.io.InvalidClassException
        System.out.println(newUser1);
    } catch (IOException e) {
// do some exception operation
    } catch (ClassNotFoundException e) {
// do some exception operation
    } finally {
        try {
            FileUtils.forceDelete(file);
        } catch (IOException e) {
// do some exception operation
        }
    }

在进行反序列化时,Jvm会把传来的字节流中的serialVersionUID与本地实体类中serialVersionUID进 行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。

控制变量不被序列化-transient关键字

在实际开发过程中,我们常常遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化。

@Data
public class UserInfo1 implements Serializable {
    private static final long serialVersionUID = 2L;
    private String name;
    private int age;
    private transient String password;
    private transient String salt;
}

序列化应用场景

序列化保存的是对象的状态,而静态变量属于类的状态,因此,序列化并不保存静态变量。

@Data
public class UserInfo1 implements Serializable {
    public static int test=1;
    private String name;
    private int age;
    private transient String password;
    private transient String salt;
}

public static void main(String[] args) throws IOException {
        UserInfo1 user = new UserInfo1();
        user.setAge(20);
        user.setName("zhangsan");


        //将序列化写入到文件中去
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("test1.txt"))) {
            oos.writeObject(user);
        }catch (IOException e) {
            System.out.println(e.getMessage());
        }
        UserInfo1.test=2;
        try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream("test1.txt"))) {
            UserInfo1 userInfo1 =(UserInfo1) ois.readObject();
            System.out.println(userInfo1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

从输出的结果来看,静态变量并没有输出到结果中去,可见静态变量序列化和反序列化。

父类的序列化

要想将父类对象序列化,就需要让父类对象也实现Serializable接口。如果不实现的话的,就需要有默认的无参的构造函数。在父类没有实现Serializable接口时,虚拟机是不会序列化父对象的,而一个java对象构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造方法作为默认的父对象。在父类无参构造方法中对变量进行初始化,否则的话。父类变量值都是默认的声明的值的,如int型,string是null

父类实现了序列化,子类没有,他的子类都将自动的实现序列化,不需要显示实现序列化接口。

import java.io.Serializable;
@Data
public class Parent implements Serializable {
    private static final long serialVersionUID = 1L;
    private int age;
    private String name;
}

@Data
public class Child extends Parent  {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
}

  public static void main(String[] args) throws IOException {
        Child child=new Child();
        child.setName("zhangsan");
        child.setAge(20);

        //将序列化写入到文件中去
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("test3.txt"))) {
            oos.writeObject(child);
        }catch (IOException e) {
            System.out.println(e.getMessage());
        }
        File file = new File("test3.txt");
        try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file))) {
            Child child1 =(Child) ois.readObject();
            System.out.println(child1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

结果可知当我们在子类上没有实现序列化的接口时,子类也可以进行序列化和反序列化。

父类不序列化

import java.io.Serializable;
@Data
public class Parent  {
    private static final long serialVersionUID = 1L;
    private int age;
    private String name;
}

@Data
public class Child extends Parent implements Serializable  {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
}

  public static void main(String[] args) throws IOException {
        Child child=new Child();
        child.setName("zhangsan");
        child.setAge(20);

        //将序列化写入到文件中去
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("test3.txt"))) {
            oos.writeObject(child);
        }catch (IOException e) {
            System.out.println(e.getMessage());
        }
        File file = new File("test3.txt");
        try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file))) {
            Child child1 =(Child) ois.readObject();
            System.out.println(child1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

父类未实现Serializable,子类实现了的,序列化自雷实例的时候,父类的属性直接被跳过不保存。

父类未实现Serializable,子类需要序列化,1.父类需要一个无参的构造器,2.子类要先序列化自身再负责序列化分类的域。

单例模式序列化

使用单利模式时,通常希望这个对象是唯一的,但是如果该类是可序列化的,单例模式初始化的对象不一致,可以通过重写readResolve方法保证单例模式的特性

public class Singleton1 implements Serializable {
    public Singleton1() {
    }
    private static Singleton1 singleton1=new Singleton1();

    public static Singleton1 getInstance(){
        return singleton1;
    }

    private Object readResolve() {
        return singleton1;
    }

}

 public static void main(String[] args) {
        Singleton1 singleton = Singleton1.getInstance();
        // 将对象序列化后写入文件
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"))) {
            oos.writeObject(singleton);
        } catch (IOException e) {
        // do some exception operation
        }
        // 从文件中读取对象信息并反序列化为对象
        File file = new File("tempFile");
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            Singleton1 newSingleton = (Singleton1) ois.readObject();
            System.out.println("(singleton == newSingleton):" + (singleton == newSingleton));
        } catch (IOException e) {
        // do some exception operation
        } catch (ClassNotFoundException e) {
        // do some exception operation
        } finally {
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
        // do some exception operation
            }
        }
    }

无论是实现Serializable接口,或是Externalizable接口,实际上都是用readResolve()返回的对象直接替换在反序列化过程中创建的对象,反序列化创建的对象直接被垃圾回收掉。

二进制格式数据反序列化

二进制流的格式序列化数据,一般是语言原生实现。

比如java中的ObjectOutputStream,就是对象转化成二进制流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值