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

问题描述 投票:82回答:9

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

例如。

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

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

谢谢

谢卡尔

java performance set
9个回答
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)


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

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


UPDATE

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

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

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

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

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

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


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

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

这给我们带来了什么?

好吧,如果我们假设没有任何正在进行,那么任何两个不相等的集合元素具有相同的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)。

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


15
投票

在Guava Sets中有一种方法可以帮助:

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

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
投票

您有https://www.mkyong.com/java/java-how-to-compare-two-sets/的以下解决方案

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);
}

或者如果您更喜欢使用单个return语句:

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

  return set1 != null 
    && set2 != null 
    && set1.size() == set2.size() 
    && set1.containsAll(set2);
}

3
投票

如果您使用的是Guava库,则可以:

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

然后根据这些得出结论。


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
    }
}

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;
    }

-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
© www.soinside.com 2019 - 2024. All rights reserved.