在现代软件开发中,数据的传输与存储是核心环节之一。无论是分布式系统中不同节点间的数据交换,还是应用程序对对象状态的持久化保存,都离不开一种关键技术 —— 序列化与反序列化。它们如同数据的 “转换器”,让内存中的对象能够跨越边界进行传递和留存。
序列化是指将内存中的对象状态转换为可传输或可存储的二进制流、XML、JSON 等格式的过程。简单来说,就是把对象 “打包装”,变成一种能在网络上传输或者能存入文件的形式。
而反序列化则是序列化的逆过程,指将序列化后的数据(如二进制流、XML、JSON 等)重新转换为内存中对象的过程,相当于把 “包裹” 拆开,恢复成原来的对象状态。
例如,在远程调用中,客户端需要将请求参数对象序列化后发送给服务器,服务器接收到数据后进行反序列化,得到原始对象并处理;当需要保存程序的运行状态时,也可以将对象序列化后存入磁盘,下次启动时再通过反序列化恢复状态。
序列化与反序列化的核心原理是数据格式的转换与重构。在内存中,对象由属性、方法和引用关系等组成,但方法是可执行的代码,无需存储或传输,因此序列化的核心是对对象的属性数据进行处理。其基本流程如下:
(1)序列化过程:系统遍历对象的所有可序列化属性,按照一定的规则(如特定的二进制格式、标签结构等)将这些属性的值转换为字节序列或文本格式。在这个过程中,需要处理对象的继承关系、引用关系(避免循环引用导致的无限递归)等复杂情况。
(2)反序列化过程:系统读取序列化后的数据,根据数据中包含的类型信息和结构信息,重新创建对象实例,并为其属性赋值,同时恢复对象之间的引用关系,最终在内存中还原出与序列化前一致的对象结构。
不同的序列化框架(如 Java 原生序列化、JSON 序列化、Protobuf 等)采用的具体格式和规则不同,但核心都是通过 “标记数据类型 + 记录属性值 + 处理关系” 来实现对象的转换与重构。
序列化与反序列化是数据交互和持久化的基础,其作用主要体现在以下几个方面:
(1)数据传输:在分布式系统、微服务架构或客户端 - 服务器模式中,不同进程或节点运行在不同的内存空间,对象无法直接共享。通过序列化将对象转为可传输格式(如网络字节流),能实现跨进程、跨节点的数据交换。
(2)数据持久化:内存中的对象会随着程序关闭而消失,序列化可以将对象状态保存到磁盘文件、数据库等存储介质中,实现对象状态的长期留存,下次程序启动时通过反序列化恢复。
(3)对象复制:通过序列化再反序列化的过程,可以创建一个与原对象属性完全相同的新对象,实现对象的深拷贝(避免引用传递导致的修改冲突)。
(4)跨语言交互:采用 JSON、XML 等通用格式进行序列化时,不同编程语言(如 Java、Python、Go)可以通过解析相同的格式实现对象的交互,打破语言壁垒。
序列化与反序列化的优点主要有:
(1)实现数据跨边界流动:解决了内存中对象无法直接在网络传输或持久化存储的问题,是分布式系统、远程通信的基础。
(2)便于数据持久化管理:通过序列化将对象存入存储介质,能长期保存程序状态,提高数据的可用性和容错性。
(3)简化数据交互流程:统一的序列化格式(如 JSON)让不同系统、不同语言之间的数据交换更加简单,减少了格式转换的复杂度。
(4)支持对象状态复制:利用序列化与反序列化实现对象深拷贝,避免了直接引用带来的副作用,便于对象的复用和隔离。
但是其也有不少缺点,如:
(1)性能开销:序列化与反序列化需要进行数据格式转换和结构解析,会消耗一定的 CPU 和内存资源,尤其是对大型对象或高频数据交互场景,可能成为性能瓶颈。
(2)版本兼容性问题:如果类的结构发生变化(如增减属性、修改属性类型),旧版本的序列化数据可能无法正确反序列化,需要额外处理版本兼容问题。
(3)安全风险:反序列化过程可能被恶意利用,攻击者通过构造恶意序列化数据,在反序列化时执行恶意代码(如 Java 中的反序列化漏洞),导致系统被入侵。
(4)数据格式限制:不同的序列化框架支持的数据类型和格式不同,部分框架可能不支持复杂对象(如循环引用对象)的序列化,或序列化后的数据体积过大。
下面通过 Java 代码示例演示序列化与反序列化的实现,以 Java 原生的序列化机制为例(基于Serializable接口)。
import java.io.Serializable;
// 可序列化的实体类
// 实现Serializable接口,表示该类可序列化
public class User implements Serializable {
// 序列化版本号,用于版本控制
private static final long serialVersionUID = 1L;
private String name;
private int age;
// transient修饰的属性不会被序列化
private transient String introduce;
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.introduce = introduce;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + ", introduce='" + introduce + "'}";
}
}
import java.io.*;
// 序列化与反序列化工具类
public class SerializationUtils {
// 序列化:将对象写入文件
public static void serialize(Object obj, String filePath) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
oos.writeObject(obj);
}
}
// 反序列化:从文件读取对象
public static Object deserialize(String filePath) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
return ois.readObject();
}
}
}
import java.io.IOException;
public class SerializationTest {
public static void main(String[] args) {
String filePath = "user.ser";
User user = new User("浩二", 24, "是个学生");
try {
// 序列化
SerializationUtils.serialize(user, filePath);
System.out.println("序列化成功,原始对象:" + user);
// 反序列化
User deserializedUser = (User) SerializationUtils.deserialize(filePath);
System.out.println("反序列化成功,恢复对象:" + deserializedUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
序列化成功,原始对象:User{name='浩二', age=24, introduce='是个学生'}
反序列化成功,恢复对象:User{name='浩二', age=24, introduce='null'}
从结果可以看出,被transient修饰的password属性未被序列化,反序列化后为null,其他属性则成功恢复。这也体现了 Java 原生序列化的特性:仅序列化对象的非 transient 属性,且需要类实现Serializable接口并指定serialVersionUID以保证版本兼容。