ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java 序列化与反序列化

2026/10/8 9:01:41 拓冰建站 浏览量
Java 序列化与反序列化

什么是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 {//显示声明,默认为1Lprivate 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) 方法的内部。