HashSet与ArrayList包含性能

Jor*_*rge 33 java arraylist hashset

处理大量数据时,我经常发现自己在做以下事情:

HashSet<String> set = new HashSet<String> ();
//Adding elements to the set
ArrayList<String> list = new ArrayList<String> (set);
Run Code Online (Sandbox Code Playgroud)

类似于"转储"列表中集的内容.我通常这样做,因为我添加的元素通常包含我要删除的重复项,这似乎是一种删除它们的简单方法.

只考虑到这个目标(避免重复),我也可以写:

ArrayList<String> list = new ArrayList<String> ();
// Processing here
if (! list.contains(element)) list.add(element);
//More processing here
Run Code Online (Sandbox Code Playgroud)

因此无需将该集"转储"到列表中.但是,在插入每个元素之前我会做一个小的检查(我假设HashSet也这样做)

这两种可能性中的任何一种显然更有效吗?

Dic*_*ici 64

该集合将提供更好的性能(O(n)O(n^2)列表相比),这是正常的,因为集合成员资格(contains操作)是集合的目的.

对于包含HashSetO(1)比较O(n)的清单,因此,你不应该使用一个列表,如果你经常需要运行contains.

  • 如果列表只包含几个元素怎么办? (5认同)
  • 复杂性计算并不真正适用于有界问题.它的目标是了解当问题规模增加时计算变得多慢,变得无限大.也就是说,我不认为在`contains`操作的哈希集上使用列表是有利的.当然,一个集合通常会有更大的内存开销,但是如果你只有几个元素,你为什么还要关心?对于有界数据集(例如,"EnumSet")存在更有效的集合实现,但通常一个简单的哈希集应该足以满足典型的性能要求 (3认同)
  • 通常,我们已经有了一个临时列表,需要对其运行`.contains`。问题是,从哪个尺寸创建Set才有意义?在10个以下的元素都可以执行1-2微米的尺度,但是我们花时间创建一个Set。无论如何,如果有人感兴趣,这里是快速的基准测试https://gist.github.com/ibalashov/0138e850e58942569a636dffa75f0bb9 (2认同)

You*_*bit 10

ArrayList使用用于存储数据的数组.这 ArrayList.contains将具有O(n)复杂性.因此,基本上一次又一次地搜索数组将具有O(n^2)复杂性.

同时HashSet使用散列机制将元素存储到各自的桶中.HashSet对于长列表值,操作会更快.它会到达元素O(1).


urs*_*6ro 7

我做了一个测试,所以请检查结果:

对于HashSet,TreeSet,ArrayList和LinkedList中的SAME STRING项,以下是结果

  1. 50.000 UUID
    • 搜索项目:e608c7d5-c861-4603-9134-8c636a05a42b(索引25.000)
    • hashSet.contains(item)?TRUE 0 ms
    • treeSet.contains(item)?TRUE 0 ms
    • arrayList.contains(item)?是2毫秒
    • linkedList.contains(item)?TRUE 3毫秒
  2. 5.000.000 UUID
    • 搜索项目:61fb2592-3186-4256-a084-6c96f9322a86(索引25.000)
    • hashSet.contains(item)?TRUE 0 ms
    • treeSet.contains(item)?TRUE 0 ms
    • arrayList.contains(item)?是1毫秒
    • linkedList.contains(item)?是2毫秒
  3. 5.000.000 UUID
    • 搜索项目:db568900-c874-46ba-9b44-0e1916420120(索引号2.500.000)
    • hashSet.contains(item)?TRUE 0 ms
    • treeSet.contains(item)?TRUE 0 ms
    • arrayList.contains(item)?TRUE 33毫秒
    • linkedList.contains(item)?是65毫秒

基于以上结果,使用数组列表与集合没有大的区别.也许您可以尝试修改此代码并将String替换为您的Object,然后查看差异......

    public static void main(String[] args) {
        Set<String> hashSet = new HashSet<>();
        Set<String> treeSet = new TreeSet<>();
        List<String> arrayList = new ArrayList<>();
        List<String> linkedList = new LinkedList<>();

        List<String> base = new ArrayList<>();

        for(int i = 0; i<5000000; i++){
            if(i%100000==0) System.out.print(".");
            base.add(UUID.randomUUID().toString());
        }

        System.out.println("\nBase size : " + base.size());
        String item = base.get(25000);
        System.out.println("SEARCHED ITEM : " + item);

        hashSet.addAll(base);
        treeSet.addAll(base);
        arrayList.addAll(base);
        linkedList.addAll(base);

        long ms = System.currentTimeMillis();
        System.out.println("hashSet.contains(item) ? " + (hashSet.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
        System.out.println("treeSet.contains(item) ? " + (treeSet.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
        System.out.println("arrayList.contains(item) ? " + (arrayList.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
        System.out.println("linkedList.contains(item) ? " + (linkedList.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
    }
Run Code Online (Sandbox Code Playgroud)

  • "基于上述结果,使用数组列表与集合没有大的区别".从你的数字来看,情况显然不是这样; 对于500万个UUID,当元素位于Collection的中间时,ArrayList比TreeSet或HashSet至少慢33倍. (4认同)
  • 关于小时间差的经典假设:2-3 毫秒听起来并不多。现在想象一下,您的代码处于一个紧密循环中,迭代 10,000 个项目,对每个项目执行“包含”。这些额外的 2-3 毫秒只会导致额外的 20-30 秒延迟!我曾经遇到过这样的情况:通过将面向客户端的应用程序中的特定操作缩短 2-3 毫秒,我取得了令人难以置信的性能改进。只需要选择你的优化:在每小时调用一次的东西上节省 2 毫秒是没有用的,但在短时间内调用数千次的东西上节省 2 毫秒是没有用的……天哪! (2认同)

Pet*_*rey 6

如果您不需要列表,则只使用Set,这是自然的集合,如果顺序无关紧要并且您想忽略重复项,则可以使用它。

您可以同时执行两个操作,即需要一个没有重复的列表。

private Set<String> set = new HashSet<>();
private List<String> list = new ArrayList<>();


public void add(String str) {
    if (set.add(str))
        list.add(str);
}
Run Code Online (Sandbox Code Playgroud)

这样,列表将仅包含唯一值,保留了原始插入顺序,并且操作为O(1)。

  • 我要提到的是,如果顺序很重要,可以使用`LinkedHashSet`,如果有排序顺序,则可以使用`TreeSet`。 (4认同)