Java常用数据流类库与ObjectInputStream

ObjectInputStream反序列化最常见报错是java.io.InvalidClassException,因serialVersionUID不匹配或类结构变更;其他高频错误包括StreamCorruptedException、ClassNotFoundException和NotSerializableException。

ObjectInputStream 反序列化失败的典型报错是什么

最常见的是 java.io.InvalidClassException,提示 class invalid for deserializationlocal class incompatible。根本原因是序列化方和反序列化方的 serialVersionUID 不匹配,或类结构已变更(比如删了字段、改了访问修饰符)但没显式声明 serialVersionUID

其他高频错误包括:

  • java.io.StreamCorruptedException: invalid stream header:用 ObjectOutputStream 写出的字节被当作文本打开过,或传输/存储过程中损坏(如 Base64 编码未正确还原)
  • ClassNotFoundException:反序列化时找不到对应类,常见于服务端序列化、客户端无该类(尤其在微服务或 RPC 场景)
  • java.io.NotSerializableException:试图序列化未实现 Serializable 接口的对象(含其成员变量)

ObjectInputStream 不能直接读取普通文件流?

可以,但必须确保该文件是由 ObjectOutputStream 写入的二进制序列化数据。如果文件是 JSON、XML 或纯文本,ObjectInputStream 会立刻抛出 StreamCorruptedException —— 它不解析内容,只校验魔数(AC ED)和版本头。

正确做法:

  • 确认源文件由 ObjectOutputStream 写出,且未被文本编辑器修改过
  • 使用 FileI

    nputStream
    包装后传给 ObjectInputStream 构造函数
  • 务必在读取前检查流是否为空(available() > 0),否则可能阻塞或静默失败
try (FileInputStream fis = new FileInputStream("data.ser");
     ObjectInputStream ois = new ObjectInputStream(fis)) {
    MyData obj = (MyData) ois.readObject(); // 必须 catch ClassNotFoundException & IOException
}

为什么推荐用 Jackson / Gson 替代 ObjectInputStream

ObjectInputStream 是 Java 原生机制,强耦合 JVM 和类定义,不适合跨语言、长期存储或安全敏感场景。而 Jackson(ObjectMapper)和 Gson(Gson)基于 JSON,有明显优势:

  • 可读性:输出是明文 JSON,便于调试、日志查看、人工干预
  • 兼容性:JSON 是通用格式,前端 JS、Python、Go 都能直接解析
  • 演进友好:支持注解(如 @JsonIgnore@JsonProperty)控制字段映射,类增删字段不破坏旧数据
  • 安全性:默认不执行任意代码(ObjectInputStream 曾多次曝出反序列化远程代码执行漏洞)

注意:Jackson 默认不序列化 transient 字段,Gson 默认忽略 statictransient;两者都可通过配置覆盖。

ObjectInputStream 的替代方案中,哪些适合高性能二进制场景

如果必须用二进制且追求性能(如游戏状态同步、高频 IPC),ObjectInputStream 并非最优选。更合适的库包括:

  • Kryo:比原生序列化快 10x+,体积小,需注册类(kryo.register(MyClass.class)),但不兼容 Java 原生序列化格式
  • FST(Fast-Serialization):接近 Kryo 性能,无需注册,兼容部分 Serializable 类,但维护活跃度下降
  • Protobuf(配合 protobuf-java):需定义 .proto schema,生成类,但跨语言、压缩率高、向后兼容性强;适合长期协议演进

这些库都不依赖 serialVersionUID,也不受 ObjectInputStream 的类加载器限制——这点在模块化(JPMS)或热部署环境中尤为关键。

真正难处理的不是选哪个库,而是序列化策略一旦上线,就很难再安全地改字段语义或删除字段。哪怕用 Protobuf,也要提前规划 reserved 字段和弃用标记。