Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

  • Java并发

  • Java IO

    • Java IO概述
    • IO基础

    • 常用IO流

      • 文件流
      • 缓冲流
      • 数据流
      • 对象流与序列化
        • 1. 什么是序列化
        • 2. 写入对象
        • 3. 读取对象
        • 4. serialVersionUID
        • 5. transient
        • 6. 序列化对象图
        • 7. Java 原生序列化的问题
        • 8. 替代方案
        • 专家实践清单
        • Tips 快问快答
        • 9. 总结
      • 打印流
      • 管道流
    • 文件与目录操作

    • Java NIO

    • 高级主题

    • 实践与性能优化

    • 面试与设计题

    • IO实践速查

  • JVM

目录

对象流与序列化

对象流用于把 Java 对象写入字节流,或者从字节流中恢复 Java 对象。这个过程称为序列化和反序列化。

# 1. 什么是序列化

序列化是把对象转换为字节序列的过程。

反序列化是把字节序列恢复为对象的过程。

Java对象 -> ObjectOutputStream -> 字节
字节 -> ObjectInputStream -> Java对象

对象要支持 Java 原生序列化,类需要实现 Serializable 接口。

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
}

Serializable 是标记接口,没有方法。

# 2. 写入对象

try (ObjectOutputStream output = new ObjectOutputStream(
        new FileOutputStream("user.obj"))) {

    User user = new User(1L, "Alice");
    output.writeObject(user);
}

# 3. 读取对象

try (ObjectInputStream input = new ObjectInputStream(
        new FileInputStream("user.obj"))) {

    User user = (User) input.readObject();
}

readObject() 返回 Object,需要强制类型转换,并可能抛出 ClassNotFoundException。

# 4. serialVersionUID

serialVersionUID 用于标识类的序列化版本。反序列化时,JVM 会检查字节流中的版本号和当前类是否一致。

建议显式声明:

private static final long serialVersionUID = 1L;

如果不声明,JVM 会根据类结构自动计算。一旦字段、方法、访问修饰符发生变化,自动计算结果可能改变,导致旧数据无法反序列化。

# 5. transient

transient 修饰的字段不会被序列化。

private transient String password;

适合不希望持久化的敏感信息、缓存字段、临时计算结果等。

# 6. 序列化对象图

对象流会序列化整个对象图。如果 User 中引用了 Address,那么 Address 也需要可序列化。

public class User implements Serializable {
    private Address address;
}

public class Address implements Serializable {
    private String city;
}

如果某个非 transient 字段不可序列化,会抛出 NotSerializableException。

# 7. Java 原生序列化的问题

Java 原生序列化在生产环境中要谨慎使用,原因包括:

  • 格式只适合 Java,跨语言能力很差。
  • 字节内容可读性差,不利于排查问题。
  • 类结构变化后兼容性复杂。
  • 反序列化存在安全风险。
  • 性能和体积通常不如现代序列化框架。

很多安全漏洞都和不可信数据的反序列化有关。

# 8. 替代方案

生产系统中常见替代方案:

  • JSON:可读性好,适合接口和配置。
  • Protocol Buffers:性能好,跨语言,适合 RPC。
  • Avro:适合大数据生态和 Schema 演进。
  • Kryo:性能较好,但跨语言能力有限。
  • Hessian:曾常用于 Java RPC 场景。

选型时要考虑跨语言、性能、体积、可读性、Schema 演进和安全性。

# 专家实践清单

检查项 建议 风险
数据来源 只反序列化可信数据 不可信输入可能触发漏洞
类型范围 配置反序列化过滤器 任意类型实例化风险高
版本兼容 显式声明 serialVersionUID 类结构变化导致读取失败
对象图大小 限制深度和数量 内存放大或 OOM
替代方案 优先 JSON/Protobuf 等 原生序列化生态弱

对象流的隐含成本:

业务对象
  │
  ├─ 类名、字段描述
  ├─ 对象引用关系
  ├─ 字段值
  └─ 序列化协议元数据
       ▼
     字节流

# Tips 快问快答

Q:对象流解决什么问题? A:把 Java 对象图序列化为字节,并从字节恢复对象。

Q:为什么生产环境谨慎使用原生序列化? A:安全风险、兼容性、性能、跨语言和可观测性都比较差。

Q:serialVersionUID 有什么作用? A:标识序列化版本,帮助判断类结构是否兼容。

Q:不声明 serialVersionUID 会怎样? A:JVM 会自动计算,类结构变化后可能导致旧数据无法反序列化。

Q:transient 的作用是什么? A:标记字段不参与默认序列化,常用于敏感信息或可重新计算字段。

Q:反序列化最大安全风险是什么? A:攻击者构造恶意对象链,在反序列化过程中触发危险代码路径。

Q:如何降低原生反序列化风险? A:限制数据来源、配置对象输入过滤器、白名单类型、限制深度和大小。

Q:常见替代方案有哪些? A:JSON、Protocol Buffers、Avro、Thrift、Kryo 等。

# 9. 总结

对象流可以方便地保存和恢复 Java 对象,但它不是现代分布式系统中的首选序列化方案。学习它有助于理解对象如何变成字节,但生产环境要特别关注兼容性和反序列化安全。

上次更新: 2026/06/25, 14:19:18
数据流
打印流

← 数据流 打印流→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式