在Java中比较两个集合的最快方法是什么?

时间:2010-07-27 06:30:35

标签: java performance set

我正在尝试优化一段比较列表元素的代码。

例如

public void compare(Set<Record> firstSet, Set<Record> secondSet){
    for(Record firstRecord : firstSet){
        for(Record secondRecord : secondSet){
            // comparing logic
        }
    }
}

请注意,套装中的记录数量会很高。

由于

谢加

9 个答案:

答案 0 :(得分:135)

firstSet.equals(secondSet)

这实际上取决于你想要在比较逻辑中做什么...即如果你发现一个元素中的元素不在另一个元素中会发生什么?您的方法具有void返回类型,因此我假设您将在此方法中执行必要的工作。

如果您需要,可以进行更精细的控制:

if (!firstSet.containsAll(secondSet)) {
  // do something if needs be
}
if (!secondSet.containsAll(firstSet)) {
  // do something if needs be
}

如果您需要获取一组中的元素而不是另一组中的元素。
编辑:set.removeAll(otherSet)返回布尔值,而不是集合。要使用removeAll(),您必须复制该集合然后使用它。

Set one = new HashSet<>(firstSet);
Set two = new HashSet<>(secondSet);
one.removeAll(secondSet);
two.removeAll(firstSet);

如果onetwo的内容都为空,那么您就知道这两个集合是相同的。如果没有,那么你就有了使这些集不相等的元素。

您提到记录数量可能很高。如果底层实现是HashSet,则每个记录的提取都在O(1)时间内完成,因此您无法真正获得更好的结果。 TreeSetO(log n)

答案 1 :(得分:57)

如果您只是想知道这些集是否相等,equals上的AbstractSet方法大致如下所示:

    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)的实施。

如果集合很大并且只有很小一部分元素不同,那么你会得到接近最差的案例表现。


<强>更新

如果您愿意花时间在自定义集实现中,有一种方法可以改善“几乎相同”的情况。

这个想法是你需要预先计算并缓存整个集合的哈希值,这样你就可以在O(N)中得到集合的当前哈希码值。然后,您可以将两组的哈希码作为加速度进行比较。

你怎么能实现这样的哈希码?好吧,如果设置的哈希码是:

  • 为空集合为零,
  • 非空集的所有元素哈希码的XOR,

然后,每次添加或删除元素时,您都可以便宜地更新集合的缓存哈希码。在这两种情况下,您只需使用当前设置的哈希码对元素的哈希码进行异或。

当然,这假设元素哈希码是稳定的,而元素是集合的成员。它还假设元素类hashcode函数给出了良好的扩展。这是因为当两个设置的哈希码相同时,您仍然需要回退到所有元素的equals比较。


你可以进一步理解这个想法......至少在理论上是这样。

假设您的set元素类有一个方法来返回元素的加密校验和。现在通过对为元素返回的校验和进行异或来实现集合的校验和。

这给我们带来了什么?

好吧,如果我们假设没有任何事情发生,那么任何两个不相等的集合元素具有相同的N比特校验和的概率是2 -N 。并且概率2不等集具有相同的N位校验和也是2 -N 。所以我的想法是你可以将 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); } 实现为:

{{1}}

根据上述假设,这只会在2 -N 时间内给出错误答案。如果使N足够大(例如512位),则错误答案的概率可以忽略不计(例如大约10 -150 )。

缺点是计算元素的加密校验和非常昂贵,尤其是随着位数的增加。所以你真的需要一个有效的机制来记忆校验和。这可能会有问题。

答案 2 :(得分:15)

Guava Sets中有一种方法可以在这里提供帮助:

public static <E>  boolean equals(Set<? extends E> set1, Set<? extends E> set2){
return Sets.symmetricDifference(set1,set2).isEmpty();
}

答案 3 :(得分:4)

对于特定情况,有一个O(N)解决方案:

  • 这些集合都已排序
  • 按相同顺序排序

以下代码假定两个集合都基于可比较的记录。类似的方法可以基于比较器。

    public class SortedSetComparitor <Foo extends Comparable<Foo>> 
            implements Comparator<SortedSet<Foo>> {

        @Override
        public int compare( SortedSet<Foo> arg0, SortedSet<Foo> arg1 ) {
            Iterator<Foo> otherRecords = arg1.iterator();
            for (Foo thisRecord : arg0) {
                // Shorter sets sort first.
                if (!otherRecords.hasNext()) return 1;
                int comparison = thisRecord.compareTo(otherRecords.next());
                if (comparison != 0) return comparison;
            }
            // Shorter sets sort first
            if (otherRecords.hasNext()) return -1;
            else return 0;
        }
    }

答案 4 :(得分:4)

您从https://www.mkyong.com/java/java-how-to-compare-two-sets/

获得以下解决方案
.menu-hover-style-2 .main-navigation-ul > li#menu-item-43.current-menu-item > a.menu-item-link, .main-navigation-ul > li#menu-item-43.current-menu-ancestor > a.menu-item-link {
color: #000000!important;
overflow: hidden;
position: relative;
}


.menu-hover-style-2 .main-navigation-ul > li#menu-item-43.current-menu-item > a.menu-item-link:before, .main-navigation-ul > li#menu-item-43.current-menu-ancestor > a.menu-item-link:before {
position: absolute;
background: radial-gradient(ellipse, transparent, transparent 4px, #f0b49e 4px, #f0b49e 6px, transparent 6px);
background-size: 20px 18px;
width: 200px;
height: 9px;
content: "";
top: 30px;
z-index: -1;
}

.menu-hover-style-2 .main-navigation-ul > li#menu-item-43.current-menu-item > a.menu-item-link:after, .main-navigation-ul > li#menu-item-43.current-menu-ancestor > a.menu-item-link:after {
position: absolute;
background: radial-gradient(ellipse, transparent, transparent 4px, #f0b49e 4px, #f0b49e 6px, transparent 6px);
background-size: 20px 18px;
width: 200px;
height: 9px;
top: 39px;
left: 2px;
background-position: 8px -10px;
content: "";
z-index: -1;
}

或者,如果您希望使用单个return语句:

public static boolean equals(Set<?> set1, Set<?> set2){

    if(set1 == null || set2 ==null){
        return false;
    }

    if(set1.size() != set2.size()){
        return false;
    }

    return set1.containsAll(set2);
}

答案 5 :(得分:3)

如果您使用Guava库,则可以执行以下操作:

        SetView<Record> added = Sets.difference(secondSet, firstSet);
        SetView<Record> removed = Sets.difference(firstSet, secondSet);

然后根据这些得出结论。

答案 6 :(得分:2)

我会在比较之前将secondSet放在HashMap中。这样,您可以将第二个列表的搜索时间缩短为n(1)。像这样:

HashMap<Integer,Record> hm = new HashMap<Integer,Record>(secondSet.size());
int i = 0;
for(Record secondRecord : secondSet){
    hm.put(i,secondRecord);
    i++;
}
for(Record firstRecord : firstSet){
    for(int i=0; i<secondSet.size(); i++){
    //use hm for comparison
    }
}

答案 7 :(得分:1)

public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;

        Set<String> a = this;
        Set<String> b = o;
        Set<String> thedifference_a_b = new HashSet<String>(a);


        thedifference_a_b.removeAll(b);
        if(thedifference_a_b.isEmpty() == false) return false;

        Set<String> thedifference_b_a = new HashSet<String>(b);
        thedifference_b_a.removeAll(a);

        if(thedifference_b_a.isEmpty() == false) return false;

        return true;
    }

答案 8 :(得分:-1)

我认为可以使用equals方法的方法引用。我们假设没有疑问的对象类型有自己的比较方法。简单明了的例子就在这里,

Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));

Set<String> set2 = new HashSet<>();
set2.addAll(Arrays.asList("hanks","leo","bale"));

Predicate<Set> pred = set::equals;
boolean result = pred.test(set2);
System.out.println(result);   // true