如果你只是想知道集合是否相等,AbstractSet 上的equals 方法大致实现如下:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return containsAll(c);
}
注意它如何优化以下常见情况:
- 这两个对象是一样的
- 另一个对象根本不是集合,并且
- 这两组的尺寸不同。
之后,containsAll(...) 将在另一组中找到不在此组中的元素时立即返回false。但如果所有元素都存在于两个集合中,则需要对所有元素进行测试。
因此,当两个集合相等但对象不同时,性能最差。该成本通常为O(N) 或O(NlogN),具体取决于this.containsAll(c) 的实现。
如果集合很大并且仅在很小比例的元素上有所不同,您将获得接近最差情况的性能。
更新
如果您愿意在自定义集实现上投入时间,有一种方法可以改进“几乎相同”的情况。
这个想法是您需要预先计算并缓存整个集合的哈希值,以便您可以在O(1) 中获取集合的当前哈希码值。然后你可以比较这两组的哈希码作为加速。
您如何实现这样的哈希码?那么如果设置的哈希码是:
- 空集为零,并且
- 非空集的所有元素哈希码的 XOR,
然后您可以在每次添加或删除元素时廉价地更新集合的缓存哈希码。在这两种情况下,您只需将元素的哈希码与当前设置的哈希码进行异或。
当然,这假设元素哈希码是稳定的,而元素是集合的成员。它还假设元素类哈希码函数提供了良好的传播。那是因为当这两个集合的哈希码相同时,您仍然必须回退到所有元素的 O(N) 比较。
你可以把这个想法更进一步......至少在理论上。
警告 - 这是高度推测性的。如果您愿意,可以进行“思想实验”。
假设您的设置元素类有一个方法可以返回元素的加密校验和。现在通过对元素返回的校验和进行异或来实现集合的校验和。
这对我们有什么好处?
好吧,如果我们假设没有任何秘密发生,那么任何两个不相等的集合元素具有相同的 N 位校验和的概率是 2-N。并且 2 个不相等的集合具有相同的 N 位校验和的概率也是 2-N。所以我的想法是你可以将equals 实现为:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return checksums.equals(c.checksums);
}
根据上述假设,这只会在 2-N 次内给你一次错误的答案。如果你使 N 足够大(例如 512 位),则错误答案的概率可以忽略不计(例如大约 10-150)。
缺点是计算元素的加密校验和非常昂贵,尤其是随着位数的增加。所以你真的需要一个有效的机制来记忆校验和。这可能是有问题的。
另一个缺点是,非零的错误概率可能是不可接受的,无论概率有多小。 (但如果是这样的话......你如何处理宇宙射线翻转一个关键位的情况?或者如果它在冗余系统的两个实例中同时翻转同一个位?)