跳到主要内容
版本:1.6.0

Java 序列化格式

范围

本文档规定 Apache Fory Java 原生二进制格式,即 Java 在配置 withXlang(false) 时使用的格式。该格式针对 Java 对象图、Java 集合实现、 Java 基本类型数组、Java 类注册、Java 序列化钩子和可选的 Schema 演进进行了优化。

Java 原生模式和 xlang 模式共享小端序数值载荷、变长整数编码、引用标志、 元字符串编码以及 TypeDef/ClassDef 概念等底层构建块,但二者是不同的编码格式。 在 Java 原生模式下,只有从 BOOLSTRING 的标量类型 ID 与 xlang 共享。 除非本文档另有明确说明,否则集合、映射、结构体、数组、枚举和 Java 原生实现的类型 ID 均为 Java 原生 ID。

跨语言格式请参阅 Xlang 序列化格式

流布局

Java 原生流包含一个头字节,后跟一个或多个根对象。每个根对象都编码为一个普通对象槽:

| header | root_0 | root_1 | ... |

root:
| reference flag | [type metadata] | [value payload] |

所有多字节定长值均采用小端序。大端序 Java 实现仍然必须写入和读取小端序载荷。

流是有状态的。类型元信息、类定义和对象引用会在首次遇到时分配索引, 并可在同一流的后续位置通过索引引用。

头部

头部为单个字节:

| bits 7..2 reserved | bit 1 out-of-band | bit 0 xlang |
  • Java 原生模式下,xlang 必须为 0
  • out-of-band 在值为 1 时表示已经配置 BufferCallback
  • 保留位必须为 0

Java 原生模式不会在头部之后写入语言 ID。

引用槽

对象、可空字段和启用引用跟踪的字段使用标准 Fory 引用槽。第一个字节为有符号值:

标志字节后续载荷
NULL_FLAG-3无载荷。该槽的值为 null
REF_FLAG-2先前对象的 varuint32 引用 ID。
NOT_NULL_VALUE_FLAG-1值载荷。本次出现不会分配引用 ID。
REF_VALUE_FLAG0值载荷。在读取数据之前分配下一个引用 ID。

当某个槽禁用引用跟踪时,写入端仅使用 NULL_FLAGNOT_NULL_VALUE_FLAG

基本类型字段的快速路径不会用引用槽包裹非空基本类型值。 装箱基本类型和其他可空值使用字段元信息所选择的槽。

类型元信息

动态对象槽会先写入类型元信息,再写入值载荷。类型元信息用于标识序列化器, 并在需要时携带类名或 ClassDef 元信息。

| varuint32 type_id | [type-specific metadata] |

已注册 Java 类、Java 原生内置类型和 Fory 内部序列化器使用数值类型 ID。 未注册的类或按名称注册的类携带名称元信息。支持 Schema 演进的类可以携带 ClassDef。

原生类型 ID 范围

范围含义
0UNKNOWN,在元信息中用于动态位置或对象类型位置。
1..21BOOLSTRING 的共享标量 ID。
22..63Java 原生模式中为 xlang 内部 ID 范围保留。
64..68为未来的 Java 原生内部 ID 保留。
69..98下文列出的 Java 原生内置类型。
99+由 Java ClassResolver 分配的用户类和 Fory 类 ID。

共享标量 ID 如下:

ID名称Java 值域
1BOOLxlang 元信息中的布尔值
2INT8有符号 8 位整数元信息
3INT16有符号 16 位整数元信息
4INT32定长有符号 32 位整数元信息
5VARINT32变长有符号 32 位整数元信息
6INT64定长有符号 64 位整数元信息
7VARINT64变长有符号 64 位整数元信息
8TAGGED_INT64带标签的有符号 64 位整数元信息
9UINT8无符号 8 位整数元信息
10UINT16无符号 16 位整数元信息
11UINT32定长无符号 32 位整数元信息
12VAR_UINT32变长无符号 32 位整数元信息
13UINT64定长无符号 64 位整数元信息
14VAR_UINT64变长无符号 64 位整数元信息
15TAGGED_UINT64带标签的无符号 64 位整数元信息
16FLOAT8保留的 8 位浮点数元信息
17FLOAT16半精度浮点数元信息
18BFLOAT16bfloat16 元信息
19FLOAT3232 位浮点数元信息
20FLOAT6464 位浮点数元信息
21STRINGJava String

Java 原生内置类型从 ID 69 开始:

ID名称Java 类型或序列化器所有者
69VOID_IDjava.lang.Void
70CHAR_IDjava.lang.Character
71PRIMITIVE_VOID_IDvoid
72PRIMITIVE_BOOL_IDboolean
73PRIMITIVE_INT8_IDbyte
74PRIMITIVE_CHAR_IDchar
75PRIMITIVE_INT16_IDshort
76PRIMITIVE_INT32_IDint
77PRIMITIVE_FLOAT32_IDfloat
78PRIMITIVE_INT64_IDlong
79PRIMITIVE_FLOAT64_IDdouble
80PRIMITIVE_BOOLEAN_ARRAY_IDboolean[]
81PRIMITIVE_BYTE_ARRAY_IDbyte[]
82PRIMITIVE_CHAR_ARRAY_IDchar[]
83PRIMITIVE_SHORT_ARRAY_IDshort[]
84PRIMITIVE_INT_ARRAY_IDint[]
85PRIMITIVE_FLOAT_ARRAY_IDfloat[]
86PRIMITIVE_LONG_ARRAY_IDlong[]
87PRIMITIVE_DOUBLE_ARRAY_IDdouble[]
88STRING_ARRAY_IDString[]
89OBJECT_ARRAY_IDObject[] 和对象数组序列化器
90ARRAYLIST_IDjava.util.ArrayList
91HASHMAP_IDjava.util.HashMap
92HASHSET_IDjava.util.HashSet
93CLASS_IDjava.lang.Class
94EMPTY_OBJECT_ID空对象序列化器
95LAMBDA_STUB_IDLambda 替换类型 ID
96JDK_PROXY_STUB_IDJDK 代理替换类型 ID
97REPLACE_STUB_IDwriteReplace/readResolve 类型 ID
98NONEXISTENT_META_SHARED_ID未知类标记类型 ID

已注册类、命名类和未注册类

Java 原生模式支持三种类标识形式:

  • ID 注册:类型 ID 为已注册的数值类 ID。
  • 名称注册:类型元信息携带命名空间和类型名称字符串。
  • 未注册类:类型元信息以包名作为命名空间,并携带 Java 简单类名作为类型名称。

类注册是速度最快、最紧凑的形式。需要稳定名称或禁用类注册时使用基于名称的形式。

元信息共享

启用元信息共享后,类元信息只写入一次,随后通过流内局部索引引用:

| varuint32 marker | [class definition bytes if new] |

marker = (index << 1) | flag
flag = 0: new definition, class definition bytes follow
flag = 1: reference to an earlier definition

索引按照首次使用顺序分配。

Schema 模式

Java 原生模式有两种对象 Schema 模式。

同 Schema 模式

禁用兼容模式时使用同 Schema 模式。写入端和读取端必须具有匹配的字段及字段顺序。 普通已注册类不需要每个对象都携带 ClassDef。字段值按协议顺序直接写入。

兼容模式

兼容模式会为类似结构体的类写入 ClassDef 元信息。读取端按标识符将本地字段与远端 ClassDef 字段匹配,读取匹配字段,并使用远端字段类型元信息跳过未知字段。 兼容模式是 Java 原生格式实现 Schema 演进的路径。

在兼容模式下,如果远端值可以由本地标量 Schema 表示且不改变逻辑值, 匹配字段便可在直接顶层标量 ClassDef Schema 之间读取。这仅是一种读取适配: 写入端仍发出其本地规范字段 Schema 和载荷;ClassDef 元信息、同 Schema 模式、 动态值序列化和未知字段跳过仍将原始字段 Schema 视为不同类型。

该规则仅适用于匹配字段的直接 Schema,不适用于动态根值、映射键、映射值、集合元素、 数组元素、枚举值、时间值、二进制值、结构体或嵌套泛型/容器位置。

标量域包括 Java 布尔类型/装箱布尔类型、String、Java 基本类型和装箱数值标量字段、 其 ClassDef 元信息标识更窄数值编码域的 Fory 标量注解,以及作为精确十进制数值标量的 BigDecimal。对于不在与 xlang 共享的类型 ID 范围内、仅 Java 原生模式使用的元信息, 仍必须使用 Java ClassDef 元信息标识标量域。兼容标量转换仅在远端和本地顶层 ClassDef 字段元信息的 trackingRef = false 时适用;如果任一匹配字段的 trackingRef = true, 则在构建兼容布局时,标量类型变更属于 Schema/类型不兼容。顶层 trackingRef 和 空值/可选封装匹配的相同标量 ClassDef 字段类型是同 Schema 精确直接读取, 而不是兼容标量转换。顶层 trackingRef 封装不同的相同标量 ClassDef 字段类型属于 Schema/类型不兼容,因为其编码封装不同。空值/可选封装不同的相同标量 ClassDef 字段类型,在两个字段均为 trackingRef = false 时,仍可使用下文的可空/可选组合规则。

兼容标量转换遵循 xlang 标量转换约定:

  • String 转布尔类型仅接受 "0""1""false""true"。 布尔类型转 String 生成 "false""true"
  • 数值转布尔类型仅接受精确的零和一。布尔类型转数值会在本地数值域中生成精确的零或一。
  • 仅当本地数值域能够表示相同数学值时,数值到数值的转换才会成功;这包括范围检查、 符号性检查、精确的整数/浮点往返检查、保留浮点有符号零,以及拒绝在不同浮点类型 ID 之间转换 NaN
  • BigDecimal 作为精确数值标量参与转换。转换得到的十进制值采用规范 scale: 零和非零整数使用 scale 0;有限小数使用能够保留数学值且使 unscaled 值不能被 10 整除的最小非负 scale。在任意精度解析之前,兼容转换会拒绝长度超过 320 字节的数值字符串。 在构造较大的十的幂或格式化普通十进制文本之前,它还会拒绝规范指数或 scale 计算超过 256 位限制的已转换十进制值。同类型 BigDecimal 读取保留普通十进制载荷。
  • String 转数值只接受 xlang 序列化规范中的有限兼容数值字面量语法, 随后执行相同的无损目标域检查。NaN、无穷大、空白字符、前导加号、Unicode 十进制数字、 下划线、分组分隔符、非十进制进位制和类型后缀都会导致失败。
  • 数值转 String 会发出规范的有限数值文本:整数使用普通十进制文本; 浮点值使用带小数点并保留有符号零的精确普通十进制文本;BigDecimal 值使用不带指数记法、 且不含无意义尾随小数零的精确普通十进制文本。

当匹配的顶层字段 Schema 为 trackingRef = false 时,可空字段、装箱载体和基本类型默认值 可与标量转换组合使用。读取端首先消费远端 ClassDef 字段元信息描述的远端空值/可选封装。 存在的值在转换后赋值或包装到本地载体中。为 null 或不存在的远端值使用本地字段已有的 兼容模式缺失值/null 处理方式。不支持启用引用跟踪的标量转换。

在构建兼容布局时,标量转换矩阵之外的 Schema 对仍属于 Schema/类型兼容性错误。 一旦匹配字段被接受为标量转换操作,无效载荷值便是反序列化数据错误,必须报告为 org.apache.fory.exception.DeserializationException,而不是 Schema 缺失或注册错误。

字段顺序

Java 原生对象序列化器使用与当前 xlang 协议相同的确定性字段顺序类别:

  1. 非空基本数值和布尔标量字段。
  2. 可空基本数值和布尔标量字段,包括 Java 基本类型的装箱类。
  3. 非基本类型字段。

基本类型分组沿用基本类型比较器:

  1. 定长基本类型编码排在压缩或变长基本类型编码之前。
  2. 位宽较大的基本类型排在位宽较小的基本类型之前。
  3. 按内部基本类型 ID 升序排列。
  4. 字段标识符。

非基本类型字段直接按字段标识符排序。非基本类型 ID、序列化器种类、集合种类、 映射种类和 Java 实现类均不参与字段排序。

字段标识符按以下规则选择:

  • 如果字段具有显式非负 @ForyField(id = ...),该数值 ID 即为字段标识符。
  • 否则,将转换为 snake_case 的 Java 字段名用作字段标识符。
  • 负数注解值不是有效字段 ID。注解默认值 -1 表示没有显式 ID,选择标识符时会忽略它。

标识符比较规则如下:

  1. 如果两个字段都有显式 ID,则按数值比较 ID。
  2. 如果只有一个字段有显式 ID,基于 ID 的字段排在基于名称的字段之前。
  3. 如果两个字段都没有显式 ID,则按字典序比较 snake_case 名称。
  4. 如果标识符相同,则使用声明类和原始字段名等确定性决胜条件。 同一个类中具有相同 snake_case 标识符的无标签字段无效。 如果子类字段隐藏具有相同 Java 字段名的继承字段,xlang TypeDef 元信息中只保留最近的字段, 因为继承字段没有不同的无标签标识符。

生成的序列化器可以为基本类型、集合、映射、内置类型和用户定义序列化器保留独立的内部描述符分组, 以便生成专用快速路径。这些内部分组属于实现细节,不得改变编码字段顺序。

ClassDef 编码

兼容模式和元信息共享将 Java 类定义编码为 TypeDef 记录。TypeDef 由 8 字节头部和类元信息字节组成:

| 8-byte header | [varuint32 extra_size] | class metadata bytes |

头部位布局:

| 52-bit hash | 3 reserved bits | 1 compress bit | 8 size bits |
  • size:低 8 位。如果值为 0xff,则将 extra_size 读取为 varuint32 并加到 0xff 上。
  • compress:类元信息字节由所配置的元信息压缩器压缩时置位。
  • reserved:必须为零。
  • hash:52 位值,根据 class_metadata_bytes || header_low12_le 使用 MurmurHash3 x64_128(种子为 47)得出。header_low12_le 是头部低 12 位编码成的两个小端序字节, 其中第二个字节的高四位清零。取 MurmurHash3 结果的 lane 0,左移 12 位并采用有符号 64 位回绕, 应用有符号绝对值,再与 0xfffffffffffff000 做掩码运算。

类元信息主体

| root_kind_and_layer_count | class_layer_0 | class_layer_1 | ... |

class_layer:
| varuint32 class_header | [registered type IDs or names] | field_info... |

root_kind_and_layer_count 的高四位存储根 TypeDef 种类,低四位存储 (num_layers - 1)。如果低四位为 0b1111,则再读取一个 varuint32 并加到 15 上。

根种类代码:

代码种类
0STRUCT
1COMPATIBLE_STRUCT
2NAMED_STRUCT
3NAMED_COMPATIBLE_STRUCT
4ENUM
5NAMED_ENUM
6EXT
7NAMED_EXT
8TYPED_UNION
9NAMED_UNION
10-14保留
15扩展种类转义,在定义之前予以拒绝

class_header = (num_fields << 1) | registered_flag

  • 如果 registered_flag == 1,则将类类型 ID 写为一个字节。对于用户注册的 ENUMSTRUCTCOMPATIBLE_STRUCTEXTTYPED_UNION,将用户类型 ID 写为 varuint32
  • 如果 registered_flag == 0,则将命名空间和类型名称写为元字符串。

类层从父类到叶子类依次编码。每一层中的字段列表使用上文定义的字段顺序。

读取端可以拒绝超过运行时资源限制的 TypeDef,例如元信息主体最大字节数或单个 TypeDef 最大字段数。这些限制属于接收端资源控制,不会改变 TypeDef 编码、类型标识、动态类加载、 未知类处理、注册策略或 Schema 演进语义。

字段信息

每个字段编码如下:

| field_header | [extended_name_or_id_size] | [field name bytes] | field_type |

field_header 位布局:

含义
0trackingRef
1nullable
2..3字段名称编码
4..6编码名称长度减一,或紧凑 tag ID
7保留,必须为零

字段名称编码:

代码编码
0UTF-8
1all-to-lower 特殊编码
2lower/upper/digit 特殊编码
3tag ID;省略字段名称字节

对于名称编码,位 4..6 存储 encoded_length - 1,前提是该值小于 7。 如果值为 7,则再读取一个 varuint32 并加到 7 上。

对于 tag ID 编码,位 4..6 存储数值字段 ID,前提是该 ID 小于 7。 如果值为 7,则再读取一个 varuint32 并加到 7 上。字段 ID 必须为非负数。 同一个 TypeDef 中不能出现重复字段 ID。

字段类型

字段类型描述兼容读取端如何读取或跳过字段载荷。顶层字段类型只写入类型标签。 嵌套字段类型将 nullabletrackingRef 存储在低位中:

nested_field_type_header = (type_tag << 2) | (nullable << 1) | trackingRef

类型标签:

标签字段类型载荷
0Object/动态
1Map键字段类型、值字段类型
2Collection/List/Set元素字段类型
3Java 数组维数、组件字段类型
4Enum
5+已注册或内置类型tag - 5 为类型 ID

元字符串

命名空间、类型名称和字段名称使用 xlang 规范定义的元字符串编码。 元字符串头部存储字节长度和编码种类;扩展长度写为 varuint32

包名和命名空间名称使用 UTF-8、all-to-lower 特殊编码或 lower/upper/digit 特殊编码。 类型名称使用 UTF-8、lower/upper/digit 特殊编码、first-to-lower 特殊编码或 all-to-lower 特殊编码。字段名称使用上文的字段信息编码表。

基本类型值

当字段序列化器在静态时已知,基本类型值不携带类型元信息:

Java 类型载荷
boolean一个字节:01
byte一个有符号字节
char两字节 UTF-16 码元,小端序
short两字节有符号整数,小端序
int定长 int32 小端序,或配置后使用 ZigZag varint32
long定长 int64 小端序、ZigZag varint64,或配置后使用 tagged int64
floatIEEE 754 binary32,小端序
doubleIEEE 754 binary64,小端序

装箱基本类型在选定的空值/引用槽之后使用相同的值载荷。

大数值范围

Java 原生 BigIntegerBigDecimal 序列化器接受的整数或 unscaled 值的绝对值大小, 最多为 10_000 个规范无符号二进制字节。BigDecimal 还只接受 [-10_000, 10_000] 范围内的 scale。

这些是可接受值限制,并非对原生或 xlang 编码的更改。 Java 有符号二进制补码原生主体中的前导符号字节不计入大小限制。 写入端会在写入值的任何部分之前拒绝超出范围的值。读取端会在分配主体或构造 BigIntegerBigDecimal 之前验证逻辑大小,并保留现有的可读字节数、长度、 溢出和规范性检查。

兼容标量转换继续使用其独立的 256 位数字限制和 scale/输出扩展限制。

字符串值

Java 字符串编码如下:

| varuint36_small7 header | bytes |

header = (num_bytes << 2) | coder

coder 值:

编码
0Latin-1
1UTF-16 小端序
2UTF-8

num_bytes 是编码载荷的字节长度。

枚举值

枚举值载荷取决于配置:

  • Ordinal 模式将枚举序号写为 varuint32
  • @ForyEnumId 模式将配置的非负枚举 tag 写为 varuint32
  • Name 模式将枚举常量名称写为元字符串。

根据 Java API 约定,@ForyEnumId 可以声明在枚举常量、一个整数字段, 或一个无参数整数 getter 上。重复或负数枚举 tag 无效。

数组

基本类型数组

基本类型数组写入长度前缀和连续的小端序元素载荷:

| varuint32 byte_length | raw element bytes |

压缩的 int[]long[] 数组使用元素数量,后跟压缩元素:

int[] compressed:
| varuint32 length | varint32... |

long[] compressed:
| varuint32 length | varint64 or tagged_int64... |

byte[] 使用二进制序列化器,写入 varuint32 length,后跟原始字节。

对象数组

对象数组写入数组长度和元素类型模式:

| varuint32_small7 (length << 1 | monomorphic_flag) |
| [shared element class metadata] |
| element slots... |
  • 如果 monomorphic_flag == 1,所有非空元素使用同一个元素序列化器, 共享元素类元信息只写入一次。
  • 如果 monomorphic_flag == 0,每个非空元素都写入自己的类型元信息。

每个可空或启用引用跟踪的元素,仍在其元素载荷之前由一个引用槽表示。

集合

Java 集合序列化器写入集合大小、元素标志、可选的共享元素类型元信息和元素载荷:

| varuint32_small7 size | elements_header | [element type metadata] | elements... |

elements_header 位布局:

含义
0启用元素引用跟踪
1至少一个元素可能为 null
2使用声明的元素类型
3所有非空元素共享一种类型

当所有非空元素共享一种类型且不使用声明的元素类型时, 共享元素类型元信息只在元素载荷之前写入一次。否则,每个非空元素都写入自己的类型元信息。 null 和引用标志遵循引用槽规则。

集合子类

受支持 JDK 集合子类的专用序列化器会在元素载荷之前写入子类自身的字段层:

| varuint32_small7 size |
| [comparator reference for sorted/priority collections] |
| varuint32_small7 num_class_layers |
| class_layer_fields... |
| elements_header | [element type metadata] | elements... |

num_class_layers 是载荷中编码的子类字段层的确切数量。 如果载荷的层数与本地序列化器不匹配,读取端必须拒绝该载荷, 因为值载荷没有携带足以跳过不匹配子类布局的层标识信息。

映射

映射先写入条目数量,再写入一个或多个分块。每个分块将键和值元信息兼容的条目分组:

| varuint32_small7 size | chunk... |

非空分块:

| header | uint8 chunk_size | [key type metadata] | [value type metadata] | entries... |

chunk_size 的范围为 1..255

header 位布局:

含义
0启用键引用跟踪
1分块可能包含 null 键
2使用声明的键类型
3启用值引用跟踪
4分块可能包含 null 值
5使用声明的值类型

null 键或 null 值条目编码为单条目特殊分块,不携带 chunk_size 字节:

  • null 键和非 null 值:特殊 null-key 头部,后跟值载荷。
  • 非 null 键和 null 值:特殊 null-value 头部,后跟键载荷。
  • null 键和 null 值:仅 KV_NULL 头部。

EnumMap 写入条目数量、键枚举类元信息,随后写入其普通映射条目载荷:

| varuint32_small7 size | key enum class metadata | chunk... |

即使 size 为零,也存在键枚举类元信息,因此空 EnumMap 可以使用原始键类型重建。

映射子类

受支持 JDK 映射子类的专用序列化器会在条目分块之前写入子类自身的字段层:

| varuint32_small7 size |
| [comparator reference for sorted maps] |
| varuint32_small7 num_class_layers |
| class_layer_fields... |
| chunk... |

读取端必须拒绝不匹配的 num_class_layers,原因与集合子类相同。

JDK 包装器和视图

Java 原生模式为部分 JDK 包装器和视图提供序列化器:

  • 不可修改和同步的集合/映射包装器保留包装器类型元信息, 并将被包装的源集合或映射写为普通对象载荷。
  • 可识别的子列表视图保留子列表类型元信息,并写入一个由序列化器定义的模式字节。 模式 0 将可见元素写为集合载荷。模式 1 写入视图偏移量、大小和源列表引用。
  • Collections.newSetFromMap 写入后备映射载荷。
  • 不可变 JDK 集合序列化器保留列表、集合或映射的载荷语义, 并在读取时具现化等价的不可变或不可修改容器。

Android 和 JVM 实现可以为包装器载荷选择不同的具体公开后备类型, 但以上由序列化器定义的载荷模式决定其编码形态。

结构体和对象载荷

类似结构体的对象载荷按协议字段顺序包含字段值。所选序列化器定义确切的字段快速路径:

| field_0 payload | field_1 payload | ... |

对于每个字段,字段元信息决定该字段是直接写入基本类型载荷、可空槽、启用引用跟踪的槽、 类型元信息,还是专用的集合/映射/数组载荷。

兼容模式读取端使用远端 ClassDef 字段列表,按标识符映射字段。 未知字段使用其远端字段类型元信息跳过。

生成的序列化器可以拆分大型生成方法,并提升序列化器、字段偏移量、集合元信息或映射元信息。 这些代码生成决策必须保持相同的对象载荷顺序。

Throwable 载荷

Throwable 序列化器保留标准 Java throwable 状态和子类自身的字段:

| stack_trace_ref | cause_ref | message_ref |
| varuint32 suppressed_count | suppressed_ref... |
| varuint32 extra_field_count | extra_field_name/value... |
| varuint32_small7 num_class_layers |
| class_layer_fields... |

extra_field_count 为序列化器自身的扩展字段保留,当前写入为零。 读取时,num_class_layers 必须与本地 throwable 序列化器布局匹配。

替换与 Java 序列化钩子

Java 原生模式支持由序列化器处理 Java 对象替换和 Java 序列化钩子:

  • writeReplace/readResolve 值使用替换序列化器定义的替换元信息和载荷。
  • JDK 代理和 Lambda 替换使用其已注册的原生类型 ID。
  • 需要兼容 Java Object Serialization 的类型可以委托给序列化器, 由其在 Fory 对象槽内复现所需的 Java 语义。

这些序列化器仍遵循本文档中的流头部、引用槽和类型元信息规则。

未知类

启用元信息共享后,如果读取端没有远端 ClassDef 对应的本地类,Java 可以使用 NONEXISTENT_META_SHARED_ID 具现化一个未知类值。该值存储足够的字段数据, 使未知类序列化器能够保留和复制该未知值;它不会让用户代码可以使用该未知 Java 类。

带外缓冲区

当头部带外位被置位时,序列化器可以写入对外部缓冲区的引用, 而不是将所有字节内联写入。回调用于定义外部缓冲区的传输方式。 主流仍然是有效的 Fory 流,其中在由序列化器定义的载荷位置包含对这些缓冲区的引用。