JDK java.util.concurrent.ConcurrentSkipListSet.equals(Object o)实现效率

时间:2017-05-26 09:27:59

标签: java performance collections iterator openjdk

JDK中equals java.util.concurrent.ConcurrentSkipListSet的实现如下

public boolean equals(Object o) {
    // Override AbstractSet version to avoid calling size()
    if (o == this)
        return true;
    if (!(o instanceof Set))
        return false;
    Collection<?> c = (Collection<?>) o;
    try {
        return containsAll(c) && c.containsAll(this);
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }
}

但我认为下面的代码似乎效率更高

public boolean myEquals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof Set))
        return false;
    Collection<?> c = (Collection<?>) o;
    if (c.size() != this.size()) {
        return false;
    }

    Iterator ic = c.iterator();
    Iterator id = iterator();

    while (ic.hasNext() && id.hasNext()) {
        if (!ic.next().equals(id.next())) {
            return false;
        }
    }

    return true;
}

一个简单的测试也可能支持第二个equals

public class Test {
    public static void main(String[] args) {
        ConcurrentSkipListSet<Integer> set1 = new ConcurrentSkipListSet<Integer>();
        ConcurrentSkipListSet<Integer> set2 = new ConcurrentSkipListSet<Integer>();

        for (int i = 0; i < 10000000; i++) {
            set1.add(i);
            set2.add(i);
        }

        long ts = System.currentTimeMillis();
        System.out.println(set1.equals(set2));
        System.out.println(System.currentTimeMillis() - ts);

        ts = System.currentTimeMillis();
        System.out.println(myset1.myEquals(myset2));
        System.out.println(System.currentTimeMillis() - ts);
    }
}

输出结果

true
2713
true
589

在JDK评论中,This definition ensures that the equals method works properly across different implementations of the set interface.任何人都可以解释一下吗?

1 个答案:

答案 0 :(得分:0)

作为参考,OpenJDK thread导致创建JDK-8181146 ConcurrentSkipListSet.equals efficiency

  

在JDK评论中,This definition ensures that the equals method works properly across different implementations of the set interface.任何人都可以解释一下吗?

它来自Set.equals(Object)。根据文件:

  

如果指定的对象也是一个集合,则返回true,两个集合具有相同的大小,并且指定集合的​​每个成员都包含在此集合中(或者等效地,此集合的每个成员都包含在指定的集合中) 。此定义确保equals方法在set接口的不同实现中正常工作。

这意味着Set.equals实现应该由Set.contains(Object)的行为定义。然后,您将从java.util.SortedSet

引导您进入此词汇
  

请注意,如果有序集合要正确实现Set接口,则由有序集合维护的排序(无论是否提供显式比较器)必须与equals一致。 (有关与equals一致的精确定义,请参阅Comparable接口或Comparator接口。)这是因为Set接口是根据equals操作定义的,但是有序集使用compareTo(或compare)方法执行所有元素比较因此,从排序集的角度来看,这种方法被认为相等的两个元素是相等的。即使排序与equals不一致,排序集的行为也是明确定义的;它只是不遵守Set接口的一般合同。

那么为什么&#39;这包含了这个&包含这个&#39;在ConcurrentSkipListSet?首先,您要避免拨打ConcurrentSkipListSet.size(),因为:

  

请注意,与大多数集合不同,此方法不是恒定时间操作。由于这些集的异步性质,确定当前的元素数量需要遍历它们以计算它们。此外,在执行此方法期间可能会更改大小,在这种情况下返回的结果将是不准确的。因此,这种方法在并发应用程序中通常不是很有用。

第二个原因是你希望与等于&#39;。

保持一致

让我们根据您的代码制作一个残酷的例子:

private static boolean myEquals(Set o1, Set o2) {
    if (o1.size() == 1 && o2.size() == 1) {
        Iterator ic = o2.iterator();
        Iterator id = o1.iterator();

        while (ic.hasNext() && id.hasNext()) {
            if (!ic.next().equals(id.next())) {
                return false;
            }
        }
        return true;
    }
    return o1.equals(o2);
}

public static void main(String[] args) {
    print(skiplist(new BigDecimal("1.0")), tree(new BigDecimal("1.00")));
    print(skiplist(new BigDecimal("1.0")), hash(new BigDecimal("1.00")));
    print(skiplist(new BigDecimal("1.0")), identity(new BigDecimal("1.00")));
    print(skiplist(BigDecimal.ONE), identity(new BigDecimal(BigInteger.ONE, 0)));
}

private static Collection<BigDecimal> e() {
    return Arrays.asList(new BigDecimal("1.0"));
}

private static <E> Set<E> hash(E... e) {
    return new HashSet<>(Arrays.asList(e));
}

private static <E> Set<E> skiplist(E... e) {
    return new ConcurrentSkipListSet<>(Arrays.asList(e));
}

private static <E> Set<E> tree(E... e) {
    return new TreeSet<>(Arrays.asList(e));
}

private static <E> Set<E> identity(E... e) {
    Set<E> s = Collections.newSetFromMap(new IdentityHashMap<E, Boolean>());
    Collections.addAll(s, e);
    return s;
}

private static void print(Set o1, Set o2) {
    System.out.println(o1.getClass().getName()
            + "==" + o2.getClass().getName() + ": "
            + o1.equals(o2) + ": " + myEquals(o1, o2));
    System.out.println(o2.getClass().getName()
            + "==" + o1.getClass().getName() + ": " + o2.equals(o1)
            + ": " + myEquals(o2, o1));
}

哪个输出:

java.util.concurrent.ConcurrentSkipListSet==java.util.TreeSet: true: false
java.util.TreeSet==java.util.concurrent.ConcurrentSkipListSet: true: false
java.util.concurrent.ConcurrentSkipListSet==java.util.HashSet: false: false
java.util.HashSet==java.util.concurrent.ConcurrentSkipListSet: false: false
java.util.concurrent.ConcurrentSkipListSet==java.util.Collections$SetFromMap: false: false
java.util.Collections$SetFromMap==java.util.concurrent.ConcurrentSkipListSet: false: false
java.util.concurrent.ConcurrentSkipListSet==java.util.Collections$SetFromMap: false: true
java.util.Collections$SetFromMap==java.util.concurrent.ConcurrentSkipListSet: false: true

该输出显示新实现不是consistent with equals

  

当且仅当e1.compareTo(e2)== 0与c1的每个e1和e2的e1.equals(e2)具有相同的布尔值时,C类的自然排序被认为与equals一致。请注意,null不是任何类的实例,并且即使e.equals(null)返回false,e.compareTo(null)也应抛出NullPointerException。

现在我们可以通过替换元素检查来解决这个问题 ((Comparable) e1).compareTo((Comparable) e2) != 0Comparator.compare(e1, e2) != 0并添加检查以尝试确定这两个集合使用相同的顺序,但请记住,集合可以被包装,并且没有任何内容可以阻止来自hiding the fact that a set is backed by sorted set的调用者。现在,您回到了包含该内容的内容,其中包含此内容。可以处理集合包装器的equals的实现。

&#39;的另一个不错的属性包含了这个包含这个&#39;实现是equals实现不是为给定的集合创建一个迭代器对象,在最坏的情况下可能会有一个像Arrays.asList(s.toArray()).iterator()这样的实现。

不放松规范,放宽现有行为,或添加一个返回BiPredicate的集合方法来捕捉等价关系&#39;对于集合,我认为很难向JDK添加这样的优化。