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

ObjectInputStream 反序列化失败的典型报错是什么
最常见的是 java.io.InvalidClassException,提示 class invalid for deserialization 或 local 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)和版本头。
正确做法:
立即学习“Java免费学习笔记(深入)”;
- 确认源文件由
ObjectOutputStream写出,且未被文本编辑器修改过 - 使用
FileInputStream包装后传给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 默认忽略 static 和 transient;两者都可通过配置覆盖。
ObjectInputStream 的替代方案中,哪些适合高性能二进制场景
如果必须用二进制且追求性能(如游戏状态同步、高频 IPC),ObjectInputStream 并非最优选。更合适的库包括:
-
Kryo:比原生序列化快 10x+,体积小,需注册类(kryo.register(MyClass.class)),但不兼容 Java 原生序列化格式 -
FST(Fast-Serialization):接近 Kryo 性能,无需注册,兼容部分Serializable类,但维护活跃度下降 -
Protobuf(配合protobuf-java):需定义.protoschema,生成类,但跨语言、压缩率高、向后兼容性强;适合长期协议演进
这些库都不依赖 serialVersionUID,也不受 ObjectInputStream 的类加载器限制——这点在模块化(JPMS)或热部署环境中尤为关键。
真正难处理的不是选哪个库,而是序列化策略一旦上线,就很难再安全地改字段语义或删除字段。哪怕用 Protobuf,也要提前规划 reserved 字段和弃用标记。










