Java 序列化与反序列化

2024-01-01 18:32:01

什么是java序列化与反序列化?(了解)

  • 序列化:把对象转换为字节序列的过程
  • 反序列化:把字节序列恢复为对象的过程

为什么要序列化?(了解)

Java对象是运行在JVM的堆内存中,如果JVM停止后,它的生命也就戛然而止。为了能在JVM停止后,继续使用Java对象,需要将Java对象持久化(持久化就是将对象进行二进制转化为机器能识别的语言)。序列化使得对象可以脱离程序运行而独立存在
在以下情况需要使用Java序列化:

  • 想把的内存中的对象状态保存到一个文件中或者数据库中时候;
  • 想用套接字在网络上传送对象的时候;
  • 想通过RMI(远程方法调用)传输对象的时候。

什么是serialVersionUID?(熟悉)

序列化版本号ID,是一个用于标识序列化类版本的特殊字段。通常在实现 Serializable 接口的类中使用,用于确保序列化和反序列化的一致性

点击 Alt + Enter (Intelij IDEA下),可以快速添加serialVersionUID(hash值),如果无法快速新增,可进行如下操作:
setting->Editor->Inspections->搜索UID->勾选Serializable class without ‘serialVersionUID’ 可以检查实现了Serializable的类是否设置了serialVersionUID。

serialVersionUID的生成有以下三种方式:

  • 显式声明:默认的1L
  • 显式声明:根据包名、类名、继承关系、非私有的方法和属性以及参数、返回值等诸多因素计算出的64位的hash值
  • 隐式声明:未显式的声明serialVersionUID时java序列化机制会根据Class自动生成一个serialVersionUID(最好不要这样,因为如果Class发生变化,自动生成的serialVersionUID可能会随之发生变化,导致匹配不上)

常见的序列化有哪些?(了解)

  • JDK原生序列化
  • JSON序列化
  • Protobuf
  • Protostuff

序列化考虑的优先级:
安全性-》通用性-》兼容性-》性能-》效率-》空间开销

首选的还是 Hessian 与 Protobuf,因为他们在性能、时间开销、空间开销、通用性、 兼容性和安全性上,都满足了我们的要求。
Hessian 在使用上更加方便,在对象的兼 容性上更好。
Protobuf 则更加高效,通用性上更有优势。

JSON序列化和JDK序列化区别?
对于对象转化成json字符串和json字符串转化成对象,也是属于序列化和反序列化的范畴,相对于JDK提供的序列化机制,各有各的优缺点:

  • JDK序列化/反序列化:原生方法不依赖其他类库、但是不能跨平台使用、字节数较大;
  • json序列化/反序列化:json字符串可读性高、可跨平台使用无语言限制、扩展性好、但是需要第三方类库、字节数较大;

如何实现序列化?(熟悉)

  • 对于要序列化对象的类要去实现Serializable接口(自动)或者Externalizable接口(手动,需重写writeExternal 和 readExternal 方法)
  • JDK提供的ObjectOutputStream类的writeObject方法,实现序列化
  • JDK提供的ObjectInputStream类的readObject方法,实现反序列化

下面演示JDK实现序列化与反序列化,示例如下:

准备 Children 类:

package demo.serializable;
import java.io.Serializable;
public class Children implements Serializable {
    //显示声明,默认为1L
    private static final long serialVersionUID = 1L;
    public String unSerializable;
}

准备 Parent 类:

package demo.serializable;
import java.io.Serializable;
public class Parent implements Serializable {
    //显示声明serialVersionUID,值为诸多因素计算出的64位的hash值
    private static final long serialVersionUID = 2673051781680263602L;
    public String name;
    public int age;
    public transient String address;
    public Children children;
}

准备 SerializationUtility 类,进行序列化操作:

package demo.serializable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;
public class SerializationUtility {
    public static void main(String[] args) throws IOException {
        Parent parent = new Parent();
        parent.name = "father";
        parent.age = 50;
        parent.children = new Children();
        String serializedObj = serializeObjectToString(parent);
        System.out.println("Serialized Parent object to string:");
        System.out.println(serializedObj);
    }

    /**
     * 将对象序列化为 字节序列
     */
    public static String serializeObjectToString(Serializable o) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        oos.close();
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }
}

准备 DeserializationUtility 类,进行反序列操作:

package demo.serializable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
public class DeserializationUtility {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        String serializedObj = "rO0ABXNyABhkZW1vLnNlcmlhbGl6YWJsZS5QYXJlbnQlGJZu2UbxsgIAA0kAA2FnZUwACGNoaWxkcmVudAAcTGRlbW8vc2VyaWFsaXphYmxlL0NoaWxkcmVuO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAAADJzcgAaZGVtby5zZXJpYWxpemFibGUuQ2hpbGRyZW4AAAAAAAAAAQIAAUwADnVuU2VyaWFsaXphYmxlcQB+AAJ4cHB0AAZmYXRoZXI=";
        System.out.println("Deserializing Parent...");
        Parent deserializedObj = (Parent) deSerializeObjectFromString(serializedObj);

        System.out.println("Parent.name:" + deserializedObj.name);
        System.out.println("Parent.age:" + deserializedObj.age);
        //因为address字段加了 transient 修饰,不会进行序列化和反序列化,
        System.out.println("Parent.address:" + deserializedObj.address);
    }

    /**
     * 反序列化读取
     */
    public static Object deSerializeObjectFromString(String s) throws IOException, ClassNotFoundException {
        byte[] data = Base64.getDecoder().decode(s);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
        Object o = ois.readObject();
        ois.close();
        return o;
    }
}

序列化注意事项有哪些?(掌握)

在JDK实现序列化与反序列过程中,不难发现:

  • 一个对象要进行序列化,如果该对象成员变量是引用类型的,那这个引用类型也一定要是可序列化的,否则会报错(java.io.NotSerializableException)。序列化的对象存在引用类型时,引用类型一定要实现序列化接口。
  • 同一个对象多次序列化成字节序列,这多个字节序列反序列化成的对象还是同一个(使用==判断为true,因为所有序列化保存的对象都会生成一个序列化编号,当再次序列化时会去检查此对象是否已经序列化了,如果是,那序列化只会输出上个序列化的编号)。
  • 对于不想序列化的字段可以在字段类型之前加上 transient 关键字修饰(反序列化时会被赋予默认值)。
  • 静态(static)成员变量是属于类级别的,而序列化是针对对象的,所以不能序列化哦。
  • 如果序列化一个可变对象,序列化之后,修改对象属性值,再次序列化,只会保存上次序列化的编号(这是个坑注意下,有兴趣的可以去验证一下)。

序列化源码解析(了解)

原理解析如下:

  • ObjectOutputStream 对象调用 writeObject(“需要序列化的对象”),进行对象的序列化;
  • 进入 writeObject 方法,判定是否启用重写,启用则调用重写方法,否则调用 writeObject0(obj, false);
  • 进入 writeObject0 方法后,代码拉到方法结尾,可以看到多个if-esle 判断,判定是否实现了Serializable接口,若否则抛出异常NotSerializableException,若是则调用 writeOrdinaryObject(obj, desc, unshared) 方法;
  • 进入 writeOrdinaryObject 方法,方法中调用了 writeClassDesc(desc, false) ,该方法主要用于写入类的信息。写入类信息之后,会判定是否实现了Externalizable接口,进行写入对象信息,若是则调用 writeExternalData((Externalizable) obj)自定义的方法,若否则调用 writeSerialData(obj, desc);

JDK的序列化过程可以大致理解如下:
写头部:声明序列化协议、版本
写对象:类名、签名、属性名、属性类型、属性值、开头结尾数据。

源码详情如下:

ObjectOutputStream 对象调用 writeObject(“需要序列化的对象”),进行对象的序列化。

/**
 * 将对象序列化为 字节序列
 */
public static String serializeObjectToString(Serializable o) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    //进行序列化
    oos.writeObject(o);
    oos.close();
    return Base64.getEncoder().encodeToString(baos.toByteArray());
}

进入 writeObject 方法,判定是否启用重写,启用则调用重写方法,否则调用 writeObject0(obj, false),代码如下:

public final void writeObject(Object obj) throws IOException {
    //判定是否启用重写,启用则调用重写方法,否则调用 writeObject0(obj, false)
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}

进入 writeObject0 方法后,代码拉到方法结尾,可以看到多个if-esle 判断,判定是否实现了Serializable接口,若否则抛出异常NotSerializableException,若是则调用 writeOrdinaryObject(obj, desc, unshared) 方法。代码片段如下:

//ObjectOutputStream 在序列化的时候,会判断被序列化的Object是哪一种类型,String?array?enum?还是 Serializable,如果都不是的话,抛出 NotSerializableException异常。
if (obj instanceof String) {
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
} else {
    if (extendedDebugInfo) {
        throw new NotSerializableException(
            cl.getName() + "\n" + debugInfoStack.toString());
    } else {
        throw new NotSerializableException(cl.getName());
    }
}

进入 writeOrdinaryObject 方法,方法中调用了 writeClassDesc(desc, false) 方法,该方法主要用于写入类的信息写入类信息之后,会写入对象信息。判定是否实现了Externalizable接口,若是则调用 writeExternalData((Externalizable) obj) 自定义的方法,若否则调用 writeSerialData(obj, desc)。代码片段如下:

//写入类信息
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
//判定是否实现了Externalizable接口,是则调用 writeExternalData 的自定义方法,否则调用 writeSerialData 方法,写入对象信息。
if (desc.isExternalizable() && !desc.isProxy()) {
    writeExternalData((Externalizable) obj);
} else {
    writeSerialData(obj, desc);
}

至此,整个序列化的过程便结束了,如果有兴趣的可以继续深挖 writeExternalData((Externalizable) obj) 方法的内部。

文章来源:https://blog.csdn.net/glb168/article/details/135327125
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。