【问题标题】:Java Collections containsAll Weired BehaviorJava 集合包含所有奇怪的行为
【发布时间】:2011-01-25 11:28:23
【问题描述】:

我有以下代码,我使用 superList 和 subList ,我想检查 subList 实际上是 superList 的子列表。

我的对象没有实现 hashCode 或 equals 方法。我在测试中创造了类似的情况。当我运行测试时,结果显示 JDK 集合和普通集合的结果之间存在很大的性能差异。运行测试后,我得到以下输出。

Java Collection API 8953 MilliSeconds 的时间已过,结果为真 使用 Commons Collection API 78 MilliSeconds 的时间流逝&结果为真

我的问题是为什么 java collection 处理 containsAll 操作这么慢。我在那里做错了吗?我无法控制从遗留代码中获取的集合类型。我知道如果我将 HashSet 用于 superList,那么使用 JDK containsAll 操作会获得很大的性能提升,但不幸的是,这对我来说是不可能的。

package com.mycompany.tests;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

import org.apache.commons.collections.CollectionUtils;
import org.junit.Before;
import org.junit.Test;

public class CollectionComparison_UnitTest {

    private Collection<MyClass> superList = new ArrayList<MyClass>();
    private Collection<MyClass> subList = new HashSet<MyClass>(50000);

    @Before
    public void setUp() throws Exception {

        for (int i = 0; i < 50000; i++) {
            MyClass myClass = new MyClass(i + "A String");
            superList.add(myClass);
        subList.add(myClass);
    }

    @Test
    public void testIt() {
        long startTime = System.currentTimeMillis();
        boolean isSubList = superList.containsAll(subList);
        System.out.println("Time Lapsed with Java Collection API "
                + (System.currentTimeMillis() - startTime)
                + " MilliSeconds & Result is " + isSubList);

        startTime = System.currentTimeMillis();
        isSubList = CollectionUtils.isSubCollection(subList, superList);
        System.out.println("Time Lapsed with Commons Collection API "
                + (System.currentTimeMillis() - startTime)
                + " MilliSeconds & Result is " + isSubList);
    }   
}

class MyClass {
    String myString;

    MyClass(String myString) {
        this.myString = myString;
    }

    String getMyString() {
        return myString;
    }

}

【问题讨论】:

  • 我的猜测是,commons 集合会获取数据的副本,而不是原始数据的视图。这可能会对性能产生很大影响。
  • 这个测试不够好,无法得出结论。您应该围绕测试进行迭代,以确保它有足够长的时间来衡量性能,而不是说,HotSpot 优化您的代码需要多长时间。您还应该考虑一次在一个数据结构上运行测试,即单个程序调用应该只使用其中一个数据结构。这应该有助于减轻影响结果的 JIT 编译。

标签: java list collections hashset


【解决方案1】:

不同的算法:

ArrayList.containsAll() 提供 O(N*N),而CollectionUtils.isSubCollection() 提供 O(N+N+N)

【讨论】:

    【解决方案2】:

    您至少应该以相反的顺序尝试测试。你的结果很可能只是表明 JIT 编译器做得很好:-)

    【讨论】:

    • 当我尝试逆序时 使用 Commons Collection API 32 MilliSeconds 和结果为真 使用 Java Collection API 8859 MilliSeconds 和结果为真
    • @ZoFreX 我无法更改数据结构类型,如我的问题中所述。我以相反的顺序在循环中运行测试,结果始终相同,无论我循环代码多少次,我都反复尝试了 1 次、20 次、50 次和 100 次。结果始终一致。
    • 您可以执行superList.containsAll(new HashSet(subList)) 之类的操作并获得巨大的加速。
    • @maaartinus 是的,事实上我做了反向操作,即 (new HashSet(superList)).containsAll(subList) 并且它获得了良好的性能。谢谢。
    • 对不起,我错了,你的方向是正确的。我的根本没有帮助。
    【解决方案3】:

    ArrayList.containsAll 继承自 AbstractCollection.containsAll,是一个简单的循环检查行中的所有元素。每一步都是一个缓慢的线性搜索。我不知道CollectionUtils 是如何工作的,但是使用简单循环来更快地做到这一点并不难。将第二个列表转换为HashSet 是肯定的胜利。对两个列表进行排序并并行浏览它们可能会更好。

    编辑:

    CollectionUtils source code 说明了这一点。他们将这两个集合都转换为“基数图”,这对于许多操作来说是一种简单而通用的方式。在某些情况下,这可能不是一个好主意,例如,当第一个列表为空或非常短时,您实际上会浪费时间。在您的情况下,与 AbstractCollection.containsAll 相比,这是一个巨大的胜利,但您可以做得更好。

    多年后的附录

    OP 写的

    我知道如果我将 HashSet 用于 superList,那么使用 JDK containsAll 操作我会获得很大的性能提升,但不幸的是,这对我来说是不可能的。

    这是错误的。没有hashCodeequals 的类从Object 继承它们,可以与HashSet 一起使用,一切正常。除了每个对象都是唯一的,这可能是意想不到的和令人惊讶的,但 OP 的测试 superList.containsAll(subList) 做的完全一样。

    所以快速的解决方案是

    new HashSet<>(superList).containsAll(subList)
    

    【讨论】:

    • +1 用于检查源代码!这个答案比接受的答案更丰富。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-17
    • 2011-11-06
    • 2015-09-15
    • 2017-06-15
    • 2023-04-07
    相关资源
    最近更新 更多