Java面试知识点(八)序列化和反序列化

一、基本概念

  • 我们都知道一个对象只要实现了 Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,

  • 在 Java 中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用此对象。但是,我们创建出来的这些对象都存在于 JVM 中的堆(stack)内存中,只有 JVM 处于运行状态的时候,这些对象才可能存在。一旦 JVM 停止,这些对象也就随之消失;

  • 但是在真实的应用场景中,我们需要将这些对象持久化下来,并且在需要的时候将对象重新读取出来,Java 的序列化可以帮助我们实现该功能。

  • 对象序列化机制(object serialization)是 java 语言内建的一种对象持久化方式,通过对象序列化,可以将对象的状态信息保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式转换成对象,**对象的序列化可以很容易的在 JVM 中的活动对象和字节数组(流)**之间进行转换。

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

  • Java 类通过实现 java.io.Serialization 接口来启用序列化功能,未实现此接口的类将无法将其任何状态或者信息进行序列化或者反序列化。可序列化类的所有子类型都是可以序列化的。序列化接口没有方法或者字段,仅用于标识可序列化的语义。

  • 对象的序列化:
    目的:将一个具体的对象进行持久化,写入到硬盘上。
    (注意:静态数据不能被序列化,因为静态数据不在堆内存中,而是在静态方法区中)

  • java 的 transient 关键字为我们提供了便利,你只需要实现 Serilizable 接口,将不需要序列化的属性前添加关键字 transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

注意:

  1. 在序列化的时候,被 transient 或者 static 修饰的属性,不可以序列化。
  2. 一个类可以被序列化,那么它的子类也可以被序列化。
  3. 序列化可以实现深复制,而 Object 中的 clone 实现的就只是浅复制。

二、序列化示例

  • 在java中与序列化和反序列化相关的类
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @see java.io.Serializable

ObjectOutputStream 代表对象输出流:
它的 writeObject (Object obj) 方法可对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjectInputStream 代表对象输入流:
它的 readObject () 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

  • 使用serializable实现序列化
/* 实现序列化接口的类*/
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;

    private String serializeValueName;
    private transient int nonSerializeValueName;

    public String getSerializeValueName() {
        return serializeValueName;
    }

    public int getNonSerializeValueName() {
        return nonSerializeValueName;
    }

    public void setSerializeValueName(String serializeValueName) {
        this.serializeValueName = serializeValueName;
    }

    public void setNonSerializeValueName(int nonSerializeValueName) {
        this.nonSerializeValueName = nonSerializeValueName;
    }

    @Override
    public String toString() {
        return "Employee [serializeValueName = " + serializeValueName;
    }
}

/* 实现序列化的类*/
public class SerializingObject {

    public static void main(String[] args) {
        Employee employee = null;
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;

        employee = new Employee();
        employee.setSerializeValueName("xsj");
        employee.setNonSerializeValueName(1000);

        try {
            fos = new FileOutputStream("employee.ser");
            oos = new ObjectOutputStream(fos);
            oos.writeObject(employee);

            System.out.println("serializable has been written in file");

            fos.close();
            oos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

/* 实现反序列化的类*/
public class DeSerializationObject {

    public static void main(String[] args) {
        Employee employee = null;
        FileInputStream fis = null;
        ObjectInputStream ois = null;

        try {
            fis = new FileInputStream("employee.ser");
            ois = new ObjectInputStream(fis);
            employee = (Employee) ois.readObject();

            System.out.println("file has been read from serializable");

            fis.close();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("serializeValue = " + employee.getSerializeValueName());
        System.out.println("nonSerializeValue = " + employee.getNonSerializeValueName());
    }
}

/* 打印的结果为*/
serializeValue = xsj
nonSerializeValue = 0

实现serializable接口的类自动进行序列化,但是被transient修饰的字段是 无法实现序列化的

  • 关于serializable和externalizable的区别
public class EmployeeExternalizable implements Externalizable {

    private static final long serialVersionUID = 1L;

    private String username;
    private int password;

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(int password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public int getPassword() {
        return password;
    }

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

    }

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

    }
}

对上述类进行序列化的时候,是无法成功的,不会抛异常但是反序列的结果是空,也就是字段是默认值。

Externalizable 继承了 Serializable,该接口中定义了两个抽象方法:writeExternal () 与 readExternal ()。当使用 Externalizable 接口来进行序列化与反序列化的时候需要开发人员重写 writeExternal () 与 readExternal () 方法。由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用 Externalizable 进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现 Externalizable 接口的类必须要提供一个 public 的无参的构造器。

/* 重写writeExtrenal和readExtrenal方法*/
	@Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(username);
        out.writeInt(password);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        username = (String) in.readObject();
        password = in.readInt();
    }
  • 静态变量的序列化
    将对象序列化后,修改静态变量的数值,再将序列化对象读取出来,然后通过读取出来的对象获得静态变量的数值是修改之后的值而不是之前的值

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

三、关于serialVersionUID

一开始,测试的时候,有这个字段和没有这个字段,先序列化在反序列化的结果是一致的,似乎有没有这个字段是没有影响的,但是当没有这个字段,你先序列化之后,在修改实现serializable接口的属性(假设是id),之后再反序列化,这个时候就会出错啦,会抛异常:InvalidClassException

因为在 Employee类里面是没有明确的给这个 serialVersionUID 赋值,但是,Java 会自动的给我赋值的,这个值跟这个 Employee的属性相关计算出来的。保存的时候,也就是序列化的时候,那时候还没有这个id属性呢,
所以,自动生成的 serialVersionUID 这个值,在我反序列化的时候 Java 自动生成的这个 serialVersionUID 值是不同的,他就抛异常啦。
(你还可以反过来,带 ID 去序列化,然后,没 ID 去反序列化。也是同样的问题。)

所以引入serialVersionUID字段的意义重大,首先,你要是不知道这个序列化是干啥的,万一他真的如开头所讲的那样存数据库啦,socket 传输啦,rmi 传输啦。虽然我也不知道这是干啥的。你就给 Employee bean 实现了个这个接口,你没写这个 serialVersionUID 那么在后来扩展的时候,可能就会出现不认识旧数据的 bug,那不就炸啦吗。

赋值的时候可以简单的赋值为一个 1L

你可能感兴趣的:(java,面试,Java面试知识汇总)