目录
- 什么是序列化?
- Java 原生序列化
- 工作原理
- 如何使用
- 优点
- 缺点
- Hessian 序列化
- 什么是 Hessian?
- 工作原理
- 如何使用
- 优点
- 缺点
- Java 原生序列化 vs. Hessian 序列化 (核心对比)
- 总结与选型建议
什么是序列化?
序列化就是将 Java 对象转换成字节流的过程,这个字节流可以:

- 持久化:保存到文件、数据库中。
- 网络传输:在网络上发送到另一台机器。
反序列化则是相反的过程,将字节流重新恢复成原来的 Java 对象。
想象一下,你想把一个复杂的 Person 对象(包含姓名、年龄、地址列表等)通过网络发给朋友,你不能直接发这个对象,你得把它“翻译”成通用的二进制数据(字节流),对方收到后再“翻译”回来,才能得到原来的 Person 对象,这个“翻译”过程就是序列化和反序列化。
Java 原生序列化
这是 Java 语言自带的序列化机制,非常简单直接。
工作原理
Java 原生序列化的核心是 java.io.Serializable 接口,它是一个标记接口,里面没有任何方法,一个类只要实现了这个接口,就表示该类的对象可以被序列化。

序列化过程由 ObjectOutputStream 完成,反序列化由 ObjectInputStream 完成。
如何使用
步骤 1:让类实现 Serializable 接口
import java.io.Serializable;
import java.util.List;
public class User implements Serializable {
// 序列化版本号,用于版本控制,建议始终定义
private static final long serialVersionUID = 1L;
private String name;
private int age;
// transient 关键字标记的字段不会被序列化
private transient String password;
// 其他构造方法、getter/setter...
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
}
serialVersionUID:这是一个非常重要的字段,它用于在反序列化时验证发送方和接收方的对象版本是否一致,如果不一致,会抛出InvalidClassException,如果不显式定义,JVM 会根据类的结构自动生成一个,但这很危险,因为任何微小的改动(比如增加一个字段)都会导致serialVersionUID变化,从而破坏兼容性。transient:关键字,表示该字段不参与序列化,常用于序列化敏感信息(如密码)或不需要持久化的数据(如数据库连接)。
步骤 2:使用 ObjectOutputStream 和 ObjectInputStream 进行序列化和反序列化
import java.io.*;
public class JavaSerializationExample {
public static void main(String[] args) {
User user = new User("Alice", 30, "123456");
// 1. 序列化:将对象写入文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("对象已序列化到 user.ser 文件");
} catch (IOException e) {
e.printStackTrace();
}
// 2. 反序列化:从文件读取对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User deserializedUser = (User) ois.readObject();
System.out.println("对象已从文件反序列化:");
System.out.println(deserializedUser);
// 注意:password 字段因为被 transient 修饰,所以为 null
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
优点
- 简单易用:JDK 内置,无需引入额外依赖。
- 功能强大:可以处理几乎所有 Java 对象(包括集合、循环引用等)。
缺点
- 性能差:序列化后的字节流体积较大,序列化/反序列化速度慢。
- 语言耦合性强:序列化结果是 Java 特有的字节流,无法被其他语言(如 Python, C++, Go)直接解析,这限制了它在跨语言系统中的应用。
- 安全性问题:反序列化过程可能存在漏洞,如果攻击者精心构造一个恶意的字节流,反序列化时可能会执行任意代码,导致远程代码执行。
- 版本兼容性脆弱:虽然
serialVersionUID可以提供一定保障,但当类结构发生较大变化时,兼容性问题依然很棘手。
Hessian 序列化
Hessian 是一个轻量级的、跨语言的二进制序列化协议,它由 Caucho 公司开发,被广泛用于 Web 服务和数据交换。
什么是 Hessian?
Hessian 本质上是一个 RPC(远程过程调用)框架,但其核心就是它的高效序列化机制,它定义了一套二进制格式,可以被多种语言实现,如 Java, Python, C++, C#, PHP 等。
工作原理
Hessian 序列化采用二进制格式,相比 Java 原生的文本格式,它更紧凑、更高效,它通过一种类型标记系统来表示不同的数据类型(如字符串、整数、列表、Map、自定义对象等)。
如何使用
步骤 1:添加 Hessian 依赖
如果你使用 Maven,在 pom.xml 中添加:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version> <!-- 请使用最新版本 -->
</dependency>
步骤 2:让类实现 Serializable 接口
Hessian 也要求对象是 Serializable 的,Java 类的定义和之前一样。
// User.java (与之前相同)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password;
// ... 构造方法, getter/setter, toString ...
}
步骤 3:使用 Hessian2Input 和 Hessian2Output 进行序列化和反序列化
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import java.io.*;
public class HessianSerializationExample {
public static void main(String[] args) {
User user = new User("Bob", 25, "654321");
// 1. Hessian 序列化
byte[] serializedBytes;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(bos)) {
hessian2Output.writeObject(user);
hessian2Output.flush();
serializedBytes = bos.toByteArray();
System.out.println("对象已通过 Hessian 序列化,字节大小: " + serializedBytes.length);
} catch (IOException e) {
e.printStackTrace();
return;
}
// 2. Hessian 反序列化
try (ByteArrayInputStream bis = new ByteArrayInputStream(serializedBytes);
Hessian2Input hessian2Input = new Hessian2Input(bis)) {
User deserializedUser = (User) hessian2Input.readObject();
System.out.println("对象已通过 Hessian 反序列化:");
System.out.println(deserializedUser);
// password 字段同样不会被序列化
} catch (IOException e) {
e.printStackTrace();
}
}
}
优点
- 高性能:序列化后的数据体积小,速度快,远优于 Java 原生序列化。
- 跨语言:这是 Hessian 最大的优势,只要服务端和客户端都有 Hessian 的实现,就可以用不同语言进行通信。
- 协议中立:不依赖于任何特定的语言或平台。
- 相对安全:相比 Java 原生序列化,其安全漏洞较少,但任何反序列化操作都应谨慎,避免处理不可信的数据源。
缺点
- 非标准:Hessian 是一个商业公司的产品,不是一个像 JSON、XML 那样的开放标准。
- 序列化结果不可读:二进制格式,无法像 JSON 那样直接阅读和调试。
- 复杂对象支持:对于一些极其复杂的 Java 对象图(如包含内部类、匿名类等),Hessian 的支持可能不如 Java 原生序列化稳定。
Java 原生序列化 vs. Hessian 序列化 (核心对比)
| 特性 | Java 原生序列化 | Hessian 序列化 |
|---|---|---|
| 易用性 | 非常高,JDK 内置,无需额外库 | 高,需要引入 Hessian 库 |
| 性能 | 差,字节流大,速度慢 | 优秀,字节流小,速度快 |
| 跨语言 | 否,仅限 Java 生态 | 是,支持多种语言 (Python, C++ 等) |
| 数据格式 | Java 特有的二进制格式 | 跨语言的二进制协议 |
| 可读性 | 不可读 | 不可读 |
| 安全性 | 较差,存在已知反序列化漏洞 | 相对较好,但仍需警惕 |
| 版本控制 | 依赖 serialVersionUID,较脆弱 |
依赖 serialVersionUID,机制类似 |
| 应用场景 | - JVM 内部的缓存、RMI - 快速的、临时的数据持久化 |
- 微服务之间的 API 调用 - 需要跨语言通信的系统 - 对性能要求高的数据传输 |
总结与选型建议
什么时候选择 Java 原生序列化?
- 纯 Java 环境:如果你的应用只在 JVM 内部传递对象(使用 RMI,或者将对象存入 Java 自带的缓存系统如 Ehcache),并且对性能要求不高,原生序列化是最简单直接的选择。
- 快速原型开发:为了快速实现一个功能,不想引入第三方依赖时。
什么时候选择 Hessian 序列化?
- 高性能需求:当你发现原生序列化成为系统瓶颈时,Hessian 是一个极佳的替代方案。
- 跨语言服务:如果你的微服务架构中,有些服务是 Java 写的,有些是 Python 或其他语言写的,Hessian 是连接它们的理想桥梁。
- Web 服务:Hessian 常被用作 RESTful API 或 SOAP 的替代方案,提供更高效的二进制数据传输。
现代选型趋势
在当今的微服务架构中,JSON 和 Protocol Buffers (protobuf) 已经成为更主流的选择。
- JSON:基于文本,可读性极好,几乎所有语言都支持,虽然性能比二进制协议低,但因其通用性和人类可读性,在 API 开发中占据主导地位。
- Protocol Buffers (protobuf):由 Google 开发,是一种语言中立、平台中立、可扩展的序列化机制,它的性能极高,生成的数据体积比 Hessian 更小,并且自带了强大的编译工具来生成代码,是目前对性能和跨语言要求极高的场景下的首选。
- 避免在新项目中使用 Java 原生序列化,除非有非常特殊且明确的理由。
- Hessian 是一个优秀的高性能、跨语言方案,尤其适合遗留系统的改造或对性能有苛刻要求的 Java 项目。
- 对于全新的项目,特别是需要定义 API 的,优先考虑 JSON 或 Protocol Buffers。
