跳到主要内容
版本:0.16

引用跟踪

本页说明 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 的含义如下:

含义
-1NULL_FLAG值为 null
-2NOT_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>
RustRc<T>Arc<T>Weak<T>

自定义字段级引用跟踪

Java:@ForyField 注解

public class Document {
// 默认不做引用跟踪
String title;

// 为该字段启用引用跟踪
@ForyField(trackingRef = true)
Author author;

// 如果多个文档共享同一组 Tag,可启用引用跟踪避免重复
@ForyField(trackingRef = true)
List<Tag> tags;
}

C++:fory::field 包装器

struct Document {
std::string title;

// shared_ptr / SharedWeak 默认就会做引用跟踪
std::shared_ptr<Author> author;
fory::serialization::SharedWeak<Data> data;

// 使用 field 包装器时也可以显式声明
fory::field<std::shared_ptr<Tag>, 1, fory::ref> tag_owner;
};
FORY_STRUCT(Document, title, author, data, tag_owner);

如果想在 C++ 侧完全关闭引用跟踪,可以在构建序列化器时设置 Fory::builder().track_ref(false)

Rust:字段属性

use fory::ForyObject;
use std::rc::Rc;

#[derive(ForyObject)]
struct Document {
title: String,

// Rc / Arc 默认启用引用跟踪
author: Rc<Author>,

// 显式开启引用跟踪
#[fory(ref = true)]
tags: Vec<Tag>,
}

Go:结构体 Tag

type Document struct {
Title string

// 为结构体指针启用引用跟踪
Author *Author `fory:"ref"`

// 为 slice 启用引用跟踪
Tags []Tag `fory:"ref"`
}

何时启用字段级引用跟踪

以下场景建议开启:

  • 同一个对象实例可能在多个位置重复出现
  • 字段处于循环引用链路上
  • 字段中保存的是较大且可能被共享的对象

以下场景建议关闭或保持默认:

  • 字段值始终唯一
  • 字段是基础类型或简单值类型
  • 字段不会参与对象共享

示例:共享引用

public class Container {
List<String> data;
List<String> sameData; // 指向同一个列表
}

Container obj = new Container();
obj.data = Arrays.asList("a", "b", "c");
obj.sameData = obj.data; // 共享引用

// refTracking=true:data 只序列化一次,sameData 写入引用 ID
// refTracking=false:data 会被写两次

示例:循环引用

public class Node {
String value;
Node next;
}

Node a = new Node("A");
Node b = new Node("B");
a.next = b;
b.next = a; // 形成循环引用

// refTracking=true:可以正确处理
// refTracking=false:会导致无限递归错误

语言支持

语言共享引用循环引用
Java
Python
Go
C++
JavaScript
Rust否(受所有权规则限制)

性能考量

  • 额外开销:引用跟踪会为每个对象增加一次哈希查找
  • 适合开启:数据中存在共享引用或循环引用
  • 适合关闭:数据结构简单,不存在共享对象

相关主题