引用跟踪
本页说明 Fory 在跨语言序列化中如何通过引用跟踪处理共享引用与循环引用。
概述
引用跟踪带来以下能力:
- 共享引用:同一个对象被多次引用时,只序列化一次
- 循环引用:对象可以引用自身,或形成环状结构
- 内存效率:避免重复写出完全相同的对象数据
启用引用跟踪
Java
Fory fory = Fory.builder()
.withLanguage(Language.XLANG)
.withRefTracking(true)
.build();
Python
fory = pyfory.Fory(xlang=True, ref=True)
Go
fory := forygo.NewFory(
forygo.WithXlang(true),
forygo.WithTrackRef(true),
)
C++
auto fory = fory::Fory::builder().xlang(true).track_ref(true).build();
Rust
let fory = Fory::default()
.xlang(true)
.track_ref(true);
编码格式
启用引用跟踪后,可空字段在值前会写入一个 ref 标记字节:
[ref_flag] [value data if not null/ref]
其中 ref_flag 的含义如下:
| 值 | 含义 |
|---|---|
-1(NULL_FLAG) | 值为 null |
-2(NOT_NULL_VALUE_FLAG) | 值存在,且是第一次出现 |
≥0 | 指向此前已经序列化对象的引用 ID |
引用跟踪与可空性
这两个概念是相互独立的:
| 概念 | 目的 | 控制方式 |
|---|---|---|
| 可空性 | 决定字段是否可以为 null | 字段类型(如 Optional<T>)或注解 |
| 引用跟踪 | 决定是否对重复对象做去重 | 全局 refTracking 开关 |
关键行为:
- 只有可空字段才会写入 ref 标记字节。
- 即使
refTracking=true,不可空字段也不会写 ref 标记。 - 引用去重只针对多次出现的同一对象。
// 即使开启了引用跟踪,不可空字段仍然不会写 ref 标记
Fory fory = Fory.builder()
.withLanguage(Language.XLANG)
.withRefTracking(true)
.build();
字段级引用跟踪
即使全局启用了 refTracking=true,大多数字段默认也不会做引用跟踪。只有少数指针 / 智能指针类型会默认跟踪引用。
各语言默认行为
| 语言 | 默认字段级引用跟踪 | 默认会跟踪引用的类型 |
|---|---|---|
| Java | 否 | 无,需要通过注解开启 |
| Python | 否 | 无,需要通过注解开启 |
| Go | 否 | 无,需要使用 fory:"ref" |
| C++ | 是 | std::shared_ptr<T>、fory::serialization::SharedWeak<T> |
| Rust |