什么是序列化与反序列化?
什么是序列化与反序列化?
zhangchangsheng面试考察点
- 概念理解:面试官不仅仅是想知道 “对象变字节、字节变对象” 这个定义,更是想考察你是否理解序列化的本质——将内存中的对象状态转换为可存储/可传输的格式。
- 实践应用:序列化在实际开发中无处不在(RPC 调用、缓存存储、消息队列、深拷贝等),考察你是否知道何时需要序列化,以及如何选择合适的序列化方案。
- 安全意识:Java 原生序列化存在安全漏洞(反序列化攻击),考察你是否了解这些风险以及如何在生产环境中规避。
核心答案
序列化是将 Java 对象转换为字节序列的过程,便于存储到文件、数据库或进行网络传输;反序列化是将字节序列恢复为 Java 对象的过程。
| 操作 | 输入 | 输出 | 典型场景 |
|---|---|---|---|
| 序列化 | Java 对象 | 字节序列 | 存储到磁盘、网络传输、分布式调用 |
| 反序列化 | 字节序列 | Java 对象 | 从磁盘读取、接收网络数据、RPC 响应 |
一句话理解:序列化让对象 “离开 JVM”(持久化/传输),反序列化让对象 “回到 JVM”(恢复使用)。
深度解析
一、序列化与反序列化流程
flowchart TB
subgraph SER["序列化过程"]
J1["Java 对象(内存)"] --> O["ObjectOutputStream<br/>writeObject()"]
O --> B1["字节序列(二进制)"]
end
subgraph DE["反序列化过程"]
B2["字节序列(文件/网络)"] --> I["ObjectInputStream<br/>readObject()"]
I --> J2["Java 对象(内存)"]
end
B1 -->|"传输/存储"| B2
上图展示了序列化与反序列化的完整流程。关键要点:
- 序列化阶段:通过
ObjectOutputStream.writeObject()将内存中的 Java 对象转换为字节序列,可以写入文件、存入数据库、或通过网络发送。 - 传输/存储阶段:字节序列以二进制形式存在,不依赖于具体的 JVM,可以跨机器、跨时间传输。
- 反序列化阶段:通过
ObjectInputStream.readObject()将字节序列重新构建为内存中的 Java 对象,恢复对象的状态。
二、Java 原生序列化实现
基本用法
import java.io.*; |
关键点说明
| 关键字/接口 | 作用 | 说明 |
|---|---|---|
| Serializable | 标记接口 | 表示类可序列化,无任何方法 |
| serialVersionUID | 版本标识 | 保证序列化/反序列化版本一致性 |
| transient | 排除字段 | 被修饰的字段不参与序列化 |
| static | 类变量 | 不参与序列化(属于类,不属于对象) |
三、serialVersionUID 的重要性
// 场景:序列化后,类结构发生了变化 |
为什么必须显式定义 serialVersionUID?
- 如果不定义,JVM 会根据类结构自动生成一个版本号
- 类结构变化(新增/删除字段)后,自动生成的版本号会改变
- 反序列化时版本号不匹配,抛出
InvalidClassException
最佳实践:所有可序列化的类都必须显式定义serialVersionUID,使用 IDE 自动生成即可。
四、常见序列化方案对比
| 方案 | 性能 | 体积 | 可读性 | 适用场景 |
|---|---|---|---|---|
| Java 原生 | 慢 | 大 | 不可读 | 简单场景、深拷贝 |
| JSON | 中 | 中 | 可读 | 前后端交互、配置文件 |
| Protobuf | 快 | 小 | 不可读 | 高性能 RPC、微服务 |
| Hessian | 较快 | 较小 | 不可读 | Dubbo 默认序列化 |
| Kryo | 快 | 小 | 不可读 | 高性能场景、Flink |
上图对比了常见序列化方案的特点。选择建议:
- Java 原生序列化:仅用于简单场景或深拷贝,生产环境不推荐(性能差、体积大、有安全风险)
- JSON(Jackson/Gson):前后端交互首选,可读性好,调试方便,但性能和体积不如二进制方案
- Protobuf:高性能 RPC 场景首选,需要定义
.proto文件,跨语言支持好 - Hessian/Kryo:Java 内部 RPC 场景,无需定义 schema,性能优秀
五、序列化的安全风险
// 危险!不要反序列化不可信的数据 |
Java 原生序列化的安全风险:
- 反序列化攻击:攻击者构造恶意的字节序列,反序列化时会执行任意代码(RCE)
- 典型案例:Apache Commons Collections 反序列化漏洞(2015 年)
- 解决方案:
- 避免使用 Java 原生序列化处理不可信数据
- 使用 JSON 等安全的序列化方案
- 使用白名单机制限制可反序列化的类
面试高频追问
Serializable和Externalizable有什么区别?接口 序列化控制 性能 复杂度 Serializable 自动序列化所有非 transient 字段 较低 简单 Externalizable 完全自定义 writeExternal() 和 readExternal() 较高 复杂
生产环境推荐使用 Externalizable 或第三方序列化框架,性能更好。
2. 为什么 static 字段不参与序列化? 序列化的是对象的状态,而 static 字段属于类,不属于对象。所有对象共享同一个 static 字段,序列化它没有意义。
3. 父类没有实现 Serializable,子类可以序列化吗? 可以,但父类的字段不会被序列化。反序列化时,父类会调用无参构造方法初始化字段。
常见面试变体
- 变体一:”
transient关键字的作用是什么?” - 变体二:”为什么阿里开发手册不建议使用 Java 原生序列化?”
- 变体三:”RPC 框架中常用的序列化方案有哪些?”
记忆口诀
对象变字节叫序列,字节变对象叫反序列; Serializable 标记接口,serialVersionUID 版本要定好; transient 不参与,原生序列化有风险要少用。
总结
序列化将 Java 对象转换为字节序列,便于存储和传输;反序列化将字节恢复为对象。Java 原生序列化通过 Serializable 接口实现,但存在性能差、体积大、安全风险等问题,生产环境推荐使用 JSON、Protobuf 等更优方案。务必显式定义 serialVersionUID,敏感字段使用 transient 排除。
声明:本文转载自小哈的博客 《什么是序列化与反序列化?》,版权归原文作者所有。转载请保留本声明与原文链接。
评论
匿名评论隐私政策
WalineTwikoo
