【问题标题】:JDK java.util.concurrent.ConcurrentSkipListSet.equals(Object o) implementation efficiencyJDK java.util.concurrent.ConcurrentSkipListSet.equals(Object o) 实现效率
【发布时间】:2017-05-26 09:27:58
【问题描述】:

JDK中java.util.concurrent.ConcurrentSkipListSetequalsimplementatin如下

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. 谁能解释一下?

【问题讨论】:

  • 您的代码假定两个集合都是有序的。他们没有。
  • @EJP 实际上是 ConcurrentSkipListSet 扩展 NavigableSet 扩展 SortedSet
  • 请在 core-libs-dev@openjdk.java.net 邮件列表中讨论您的问题。
  • 谢谢@Fairoz,我稍后再发。
  • 对不起@EJP,我误解了,this 是有序的,但equals() 的参数,即Object o 可能没有有序。谢谢

标签: java performance collections iterator openjdk


【解决方案1】:

作为参考,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 一致。 (请参阅 Comparable 接口或 Comparator 接口以了解与等于一致的精确定义。)这是因为 Set 接口是根据等于操作定义的,但排序集使用其 compareTo(或比较)方法执行所有元素比较,因此从排序集的角度来看,此方法认为相等的两个元素是相等的。一个有序集合的行为是明确定义的,即使它的排序与equals不一致;它只是不遵守 Set 接口的一般约定。

那么为什么ConcurrentSkipListSet 中的“这个包含那个,那个包含这个”呢?首先,您要避免致电ConcurrentSkipListSet.size(),因为:

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

第二个原因是你想“与equals一致”。

让我们根据你的代码做一个残酷的例子:

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 对于类 C 的每个 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 的调用者。现在你回到了'this contains that and that contains this'的equals实现,它可以处理集合包装器。

“this contains that and that contains this”实现的另一个不错的属性是,equals 实现不会为给定的集合创建迭代器对象,在最坏的情况下,它可能有一个像 Arrays.asList(s.toArray()).iterator() 这样的实现。

如果不放宽规范,放宽现有行为,或者添加返回 BiPredicate 的集合方法来捕获集合的“等价关系”,我认为很难在 JDK 中添加这样的优化。

【讨论】:

    猜你喜欢
    • 2010-12-06
    • 2018-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-25
    • 1970-01-01
    • 2014-03-13
    相关资源
    最近更新 更多