1. Java 中的二进制序列化
二进制序列化是 Java 的标准机制,会将对象尽可能紧凑、快速地转换为字节流。为此使用类 ObjectOutputStream 和 ObjectInputStream。得到的文件是一组字节,不是为人类阅读而设计的。
之所以称为二进制,是因为一切都以“原始”形式序列化:数字、字符串、数组,甚至对象之间的引用都会变成字节。这就像把行李紧紧打包:高效又快速,但没有说明书就很难看出哪里放了什么。
在 Java 中如何工作?
假设我们有一个类 User:
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
// 构造函数、getter 和 setter
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
序列化到二进制文件
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
User user = new User("Bob", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
System.out.println("User 对象已序列化到文件 user.bin");
} catch (Exception e) {
e.printStackTrace();
}
从二进制文件反序列化
import java.io.FileInputStream;
import java.io.ObjectInputStream;
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loadedUser = (User) in.readObject();
System.out.println("从文件读取: " + loadedUser.getName() + ", " + loadedUser.getAge());
} catch (Exception e) {
e.printStackTrace();
}
注意:如果在文本编辑器中打开 user.bin 文件,你会看到类似 ¬í sr ... 的内容——这是正常现象,就是这样设计的!
二进制序列化的优点
- 紧凑与高速。 保存和读取都尽可能快速,没有多余的“装饰”。
- 会保存对象的所有字段,包括嵌套对象(前提是它们也实现了 Serializable)。
- 用于内部缓存或在 Java 程序之间传输时非常简单。
缺点
- 不可读。 人无法“偷看”内容并理解其含义。
- 对类版本高度绑定。 结构变化(添加/删除字段)可能会“破坏”旧文件的读取。
- 不同版本的 Java 和 JVM 之间存在兼容性问题。
- 不适合与其他编程语言进行数据交换。
- 安全性: 从不受信任来源反序列化数据是导致漏洞的直接途径。
2. 文本序列化格式:JSON、XML 等
二进制序列化适合内部使用,但很多时候需要在不同语言(Java、JavaScript、Python)之间传递数据,或以可读形式存储——便于配置、日志、API。此时使用文本格式:JSON、XML、YAML、CSV 等。
JSON——最流行
JSON(JavaScript Object Notation)——紧凑且可读。以下是序列化的 User 对象示例:
{
"name": "Bob",
"age": 30
}
在 Java 中处理 JSON 最常用的库有:Jackson(最流行)、Gson,以及 Moshi、JSON-B 等。
XML——程序员的老朋友
XML(Extensible Markup Language) 更“冗长”,但更加正式、严格。
<User>
<name>Bob</name>
<age>30</age>
</User>
在 Java 中处理 XML 常用标准库 JAXB(或者更早的 XStream)。
YAML、CSV 等
- YAML 与 JSON 相似,但更简洁;更常用于配置,而非序列化复杂对象。
- CSV 适用于“扁平”的表格,但不适合嵌套结构。
- 格式多种多样,但在 Java 中最常用的仍是 JSON 与 XML。
3. 格式对比:何时使用哪一种?
| 格式 | 可读性 | 紧凑性 | 速度 | 兼容性 | 使用场景 |
|---|---|---|---|---|---|
| 二进制 | 否 | ++ | ++ | 仅限 Java | 内部缓存,在 JVM 之间快速保存 |
| JSON | 是 | + | + | 任意语言 | REST API、与外部服务交换、配置 |
| XML | 是 | - | - | 任意语言 | 集成、严格的模式、遗留系统 |
- 二进制——适用于内部使用,不需要与外部系统交换且追求极致性能的场景。
- JSON——与 Web 应用、移动客户端和 REST API 交换,以及存储配置的最佳选择。
- XML——当需要严格的模式并与“企业级”方案集成时使用。
重要! 二进制序列化只适用于在 Java 程序之间传递数据,即便如此,最好仅在同一版本的程序之间使用。文本格式如 JSON 和 XML 更通用:它们适合在不同语言和平台之间交换数据,使信息更可读、更易于迁移。
4. 实践:序列化为二进制与文本格式
二进制序列化(ObjectOutputStream/ObjectInputStream)
上文已见,这里再复习一遍:
// 序列化
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
}
// 反序列化
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loadedUser = (User) in.readObject();
}
使用 Jackson 序列化为 JSON(简述)
要使用 Jackson,需要将其库添加到项目中。我们稍后会学习 Maven 与 Gradle,目前可以手动引入 JAR 文件。以下为 Maven 依赖示例:
<!-- Maven -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
序列化/反序列化示例:
import com.fasterxml.jackson.databind.ObjectMapper;
User user = new User("Bob", 30);
ObjectMapper mapper = new ObjectMapper();
try {
// 序列化为字符串
String json = mapper.writeValueAsString(user);
System.out.println(json); // {"name":"Bob","age":30}
// 序列化到文件
mapper.writeValue(new File("user.json"), user);
// 从字符串反序列化
User loadedUser = mapper.readValue(json, User.class);
// 从文件反序列化
User loadedFromFile = mapper.readValue(new File("user.json"), User.class);
} catch (Exception e) {
e.printStackTrace();
}
JSON 文件可以用任意文本编辑器打开,使数据在不同应用和语言之间都易于阅读与迁移。
5. 何时使用哪种格式:实用建议
- 内部缓存、临时文件、在 Java 程序之间快速读写:使用标准的二进制序列化。但请注意版本兼容性!
- 与外部服务交换、存储配置、与前端集成:使用 JSON(Jackson、Gson)。
- 与“企业级”系统集成且需要严格模式:XML(JAXB)。
- 需要人能打开并阅读文件:JSON 或 XML,而非二进制格式。
6. 使用序列化格式时的常见错误
错误 1:尝试序列化包含不可序列化字段的对象。 如果你的类有字段未实现 Serializable(例如流或数据库连接),二进制序列化会报错。对于 JSON 问题没那么严重,但在遇到“非标准”类型时也可能有麻烦。
错误 2:用文本编辑器打开二进制文件被“吓到”。 这是正常的!二进制文件并非供人类阅读。
错误 3:修改了类结构,旧的二进制文件无法读取。 二进制序列化对类结构的变化非常敏感——经常会出现 InvalidClassException。在 JSON/XML 中影响较小:未知的字段通常会被忽略或赋默认值。
错误 4:使用二进制序列化与外部系统交换数据。 这行不通:二进制格式只被 Java 理解,而且还要求版本匹配。
错误 5:在 JSON/XML 中忘记添加必要的注解。 一些库需要诸如 @JsonProperty、@XmlElement 等注解,否则序列化/反序列化可能不会按预期工作。
错误 6:未检查所有嵌套对象是否可序列化。 对于二进制序列化,这是常见问题;对于 JSON,如果模型中存在复杂类型也同样如此。
GO TO FULL VERSION