bigywater.tech
Java · 150

Java 序列化:一个对象如何变成字节流

一个对象怎么变成字节流、又怎么变回来?这篇直接看字节,再用对照实验验证 transient、static、构造器、共享引用和 serialVersionUID 的行为(反序列化的类型过滤与安全见 ObjectInputFilter 一篇)。

71 字节
只有 name 与 age 的对象序列化后的大小(数据只有 7 字节)
Base() 被调用
反序列化时只调用不可序列化的父类的无参构造器
InvalidClassException
没有显式 serialVersionUID 时,加字段后读旧数据的结果

字节流里有什么

实测 · 本机执行输出
  P{name="ann", age=30} 序列化后 71 字节:
    ac ed 00 05 73 72 00 06 4d 61 69 6e 24 50 2b 00
    42 11 0f 92 1f fc 02 00 02 49 00 03 61 67 65 4c
    00 04 6e 61 6d 65 74 00 12 4c 6a 61 76 61 2f 6c
    61 6e 67 2f 53 74 72 69 6e 67 3b 78 70 00 00 00
    1e 74 00 03 61 6e 6e
  可读部分:····sr··Main$P+·B········I··ageL··namet··Ljava/lang/String;xp····t··ann
  魔数 = aced(STREAM_MAGIC),版本 = 5,第 5 字节 0x73(0x73 = TC_OBJECT),第 6 字节 0x72(0x72 = TC_CLASSDESC)
  类描述里的 serialVersionUID(默认由类结构算出)= 3098549184674013180
  1000 个 P 的 ArrayList 序列化后 15110 字节,平均每个 P 15 字节(类描述只写一次,之后只写字段值和一个类引用)
  对比:name + age 的原始内容只有 7 字节

字段、构造器与共享引用

实测 · 本机执行输出
  创建对象:
    Base() 构造器被调用
    Child(String) 构造器被调用
  反序列化(此时把 static shared 改成了 7):
    Base() 构造器被调用
    name=ann(普通字段被还原)
    secret=null,count=0(transient 字段变成默认值;原来是 pw-123 与 9,字段初始化器 "pw"、5 也没有再运行)
    static shared=7(static 不在流里,保持当前值)
  同一个对象被两个字段引用:反序列化后 x == y 是 true(引用关系被保留),大小 79 字节
  两个内容相同但不是同一个的对象:反序列化后 x == y 是 false,大小 82 字节

serialVersionUID

实测 · 本机执行输出
  [没有显式 UID]
  v1 写出 71 字节;v1 的 serialVersionUID = -3505072937817754330
  v2 的 serialVersionUID = -8231464824746353903
  读取失败:InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -3505072937817754330, local class serialVersionUID = -8231464824746353903
  [显式 serialVersionUID = 1L]
  v1 写出 71 字节;v1 的 serialVersionUID = 1
  v2 的 serialVersionUID = 1
  读取成功:name=ann,email=null

分步演示

Java 序列化
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:writeObject/readObject 自定义序列化、Externalizable、readResolve/writeReplace、record 与枚举的序列化(record 序列化见后面的篇目)、不同 JDK 版本之间的互通、序列化过滤器(见 ObjectInputFilter 一篇)、性能与其它序列化库(Protobuf、Jackson)的对比
    京ICP备2026067502号-1京公网安备11010502063487号