这更像是一个我想分享的问题而不是一个问题:当使用 打印时toString()
,Java 将检测 Collection 中的直接循环(Collection 引用自身),但不会检测间接循环(Collection 引用另一个 Collection 引用第一个 - 或更多步骤)。
import java.util.*;
public class ShonkyCycle {
static public void main(String[] args) {
List a = new LinkedList();
a.add(a); // direct cycle
System.out.println(a); // works: [(this Collection)]
List b = new LinkedList();
a.add(b);
b.add(a); // indirect cycle
System.out.println(a); // shonky: causes infinite loop!
}
}
这对我来说是一个真正的问题,因为它发生在调试代码以打印出 Collection 时(当它捕捉到一个直接循环时我很惊讶,所以我错误地假设他们已经实现了一般的检查)。有一个问题:为什么?
我能想到的解释是,检查引用自身的集合非常便宜,因为您只需要存储集合(您已经拥有),但是对于更长的周期,您需要存储所有集合相遇,从根源开始。此外,您可能无法确定根是什么,因此您必须将每个集合存储在系统中 - 无论如何您都会这样做 - 但您还必须对每个集合元素进行哈希查找. 对于相对罕见的循环情况(在大多数编程中),这是非常昂贵的。(我认为)它检查直接循环的唯一原因是因为它非常便宜(一个参考比较)。
好的......我已经回答了我自己的问题 - 但我错过了什么重要的事情吗?有人想添加什么吗?
澄清:我现在意识到我看到的问题是特定于打印集合(即toString()
方法)。循环本身没有问题(我自己使用它们并且需要拥有它们);问题是Java无法打印它们。编辑Andrzej Doyle 指出它不仅仅是集合,而是任何toString
被调用的对象。
鉴于它受限于此方法,这里有一个算法来检查它:
- 根是调用第一个对象的对象
toString()
(要确定这一点,您需要维护 toString 当前是否正在进行的状态;所以这很不方便)。- 当您遍历每个对象时,您将其添加到 IdentityHashMap 以及唯一标识符(例如递增索引)。
- 但是如果这个对象已经在 Map 中,写出它的标识符。
这种方法还可以正确渲染多引用(一个被多次引用的节点)。
内存成本是 IdentityHashMap(每个对象一个引用和索引);复杂性成本是有向图中每个节点(即打印的每个对象)的哈希查找。