读取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
相关产品推荐
相关产品推荐

