You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

读取YAML文件时因锚点自引用导致无限循环的解决方法

处理SnakeYAML解析自引用YAML时的无限循环问题

问题背景

我使用Java读写YML文件时,遇到了自引用锚点触发的无限循环问题。相关YML文件内容如下:

&id001
playername: SomeName
lang: fr_FR
minerate-full-mined: *id001

其中minerate-full-mined键引用了文件第一行的锚点,导致解析过程中出现无限循环。

我的代码实现如下:

YAML初始化代码

DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(new Representer(options) {
    {
        this.representers.put(Configuration.class, (data) -> represent(((Configuration) data).self));
    }
}, options);

Configuration类核心代码

protected Map<String, Object> self;
protected Configuration defaults;
protected File file;

public Configuration() {
    this(null);
}

public Configuration(Configuration defaults) {
    this(null, new LinkedHashMap<String, Object>(), defaults);
}

public Configuration(File file, Map<?, ?> map, Configuration defaults) {
    this.file = file;
    this.self = new LinkedHashMap<String, Object>();
    this.defaults = defaults;
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
        if (entry.getValue() instanceof Map) {
            this.self.put(key, new Configuration(file, (Map<?, ?>) entry.getValue(), defaults == null ? null : defaults.getSection(key)));
        } else {
            this.self.put(key, entry.getValue());
        }
    }
}

无限循环发生在this.self.put(key, new Configuration(...));这一行。打印entry.getValue()时输出{playername: SomeName, lang: fr_FR, minerate-full-mined=(This Map)},能检测到自引用,但不知道如何处理这种映射自引用的情况,希望让SnakeYAML忽略自引用以避免无限循环。


解决方案

1. 解析阶段拦截自引用

在Configuration类的构造逻辑中,加入已处理Map的跟踪集合,避免递归包装同一个Map实例:

修改Configuration类,新增私有构造方法用于递归调用,并添加跟踪集合:

// 新增私有构造方法,传入已处理Map的跟踪集合
private Configuration(File file, Map<?, ?> map, Configuration defaults, Set<Object> processedMaps) {
    this.file = file;
    this.self = new LinkedHashMap<String, Object>();
    this.defaults = defaults;
    // 将当前Map加入跟踪集合,标记为已处理
    processedMaps.add(map);
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
        Object value = entry.getValue();
        if (value instanceof Map) {
            Map<?, ?> valueMap = (Map<?, ?>) value;
            // 若该Map已处理过,直接存入,不再递归创建Configuration
            if (processedMaps.contains(valueMap)) {
                this.self.put(key, valueMap);
            } else {
                this.self.put(key, new Configuration(file, valueMap, defaults == null ? null : defaults.getSection(key), processedMaps));
            }
        } else {
            this.self.put(key, value);
        }
    }
    // 处理完成后移除当前Map,不影响其他分支的解析
    processedMaps.remove(map);
}

// 修改原有公开构造方法,初始化跟踪集合
public Configuration(File file, Map<?, ?> map, Configuration defaults) {
    this(file, map, defaults, new HashSet<>());
}

通过这种方式,当检测到自引用的Map时,直接将原Map存入self,不再递归创建新的Configuration对象,从根源上避免无限循环。

2. 序列化阶段处理循环引用

如果是在写入YML文件时出现循环引用问题,可自定义Representer并加入循环引用检测逻辑:

DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
// 允许递归键,开启SnakeYAML的循环引用基础支持
options.setAllowRecursiveKeys(true);

Yaml yaml = new Yaml(new Representer(options) {
    {
        this.representers.put(Configuration.class, (data) -> {
            // 检查当前Configuration是否已被序列化,避免循环
            if (representedObjects.containsKey(data)) {
                return representedObjects.get(data);
            }
            Node node = represent(((Configuration) data).self);
            representedObjects.put(data, node);
            return node;
        });
    }
}, options);

利用SnakeYAML内置的representedObjects集合跟踪已序列化的对象,遇到重复引用时直接返回已生成的节点,避免循环序列化。

3. 可选:禁用锚点与引用解析

如果业务场景不需要保留YML中的锚点和引用关系,可直接禁用相关解析逻辑:

Constructor constructor = new Constructor();
// 禁用锚点和引用的解析,直接展开所有内容
constructor.setAllowRecursiveKeys(false);
Yaml yaml = new Yaml(constructor, options);

这种方式会丢失原有的引用结构,但能彻底避免自引用带来的循环问题。


内容的提问来源于stack exchange,提问作者Elikill58

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 07:35:24