【问题标题】:ArrayList .get faster than HashMap .get?ArrayList .get 比 HashMap .get 快吗?
【发布时间】:2015-01-04 17:37:27
【问题描述】:

我曾认为 HashMaps 对单个值的随机访问比 ArrayLists 更快。 . .也就是说,HashMap.get(key) 应该比ArrayList.get(index) 快,因为ArrayList 必须遍历集合的每个元素才能达到其值,而HashMap 则不然。你知道,O(1) vs O(n) 等等。

编辑: 所以我对HashMaps 的理解不够充分,因此我很困惑。此代码的结果符合预期。感谢您的许多解释。

所以我决定试一试。这是我的代码:

import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Scanner;

public class Testing
{       

    public static void main(String[] args)
    {
        ArrayList<SomeClass> alist = new ArrayList<>();
        HashMap<Short, SomeClass> hmap = new HashMap<>(4000, (float).75);
        ListIterator<SomeClass> alistiterator = alist.listIterator();
        short j = 0;
        do
        {
            alistiterator.add(new SomeClass());
            j++;
        }
        while(j < 4000);
        for (short i = 0; i < 4000; i++)
        {
             hmap.put(i, new SomeClass());
        }
        boolean done = false;
        Scanner input = new Scanner(System.in);
        String blargh = null;
        do
        {
            System.out.println("\nEnter 1 to run iteration tests.");
            System.out.println("Enter w to run warmup (recommended)");
            System.out.println("Enter x to terminate program.");
            try
            {
                blargh = input.nextLine();
            }
            catch (NoSuchElementException e)
            {
                System.out.println("Uh, what? Try again./n");
                continue;
            }
            switch (blargh)
            {
                case "1":
                    long starttime = 0;
                    long total = 0;
                    for (short i = 0; i < 1000; i++)
                    {
                        starttime = System.nanoTime();
                        iteratearraylist(alist);
                        total += System.nanoTime() - starttime;
                    }
                    total = (long)(total * .001);
                    System.out.println(total + " ns: iterating sequentially"
                                       + " through ArrayList");
                    total = 0;
                    for (short i = 0; i< 1000; i++)
                    {
                        starttime = System.nanoTime();
                        iteratearraylistbyget(alist);
                        total += System.nanoTime() - starttime;
                    }
                    total = (long)(total * .001);                   
                    System.out.println(total + " ns: iterating sequentially"
                                       + " through ArrayList via .get()");
                    total = 0;
                    for (short i = 0; i< 1000; i++)
                    {
                        starttime = System.nanoTime();
                        iteratehashmap(hmap);
                        total += System.nanoTime() - starttime;
                    }
                    total = (long)(total * .001);           
                    System.out.println(total + " ns: iterating sequentially"
                                       + " through HashMap via .next()");
                    total = 0;
                    for (short i = 0; i< 1000; i++)
                    {
                        starttime = System.nanoTime();
                        iteratehashmapbykey(hmap);
                        total += System.nanoTime() - starttime;
                    }                   
                    total = (long)(total * .001);       
                    System.out.println(total + " ns: iterating sequentially"
                                       + " through HashMap via .get()");
                    total = 0;
                    for (short i = 0; i< 1000; i++)
                    {
                        starttime = System.nanoTime();
                        getvaluebyindex(alist);
                        total += System.nanoTime() - starttime;
                    }               
                    total = (long)(total * .001);       
                    System.out.println(total + " ns: getting end value"
                                   + " from ArrayList");
                    total = 0;
                    for (short i = 0; i< 1000; i++)
                    {
                        starttime = System.nanoTime();
                        getvaluebykey(hmap);
                        total += System.nanoTime() - starttime;
                    }           
                    total = (long)(total * .001);       
                    System.out.println(total + " ns: getting end value"
                               + " from HashMap");
                    break;
                case "w":
                    for (int i = 0; i < 60000; i++)
                    {
                        iteratearraylist(alist);
                        iteratearraylistbyget(alist);
                        iteratehashmap(hmap);
                        iteratehashmapbykey(hmap);
                        getvaluebyindex(alist);
                        getvaluebykey(hmap);
                    }
                    break;
                case "x":
                    done = true;
                    break;
                default:
                    System.out.println("Invalid entry.  Please try again.");
                    break;
            }           
        }
        while (!done);
        input.close();
    }

    public static void iteratearraylist(ArrayList<SomeClass> alist)
    {
        ListIterator<SomeClass> tempiterator = alist.listIterator();
        do
        {
            tempiterator.next();
        }
        while (tempiterator.hasNext());
    }

    public static void iteratearraylistbyget(ArrayList<SomeClass> alist)
    {
        short i = 0;        
        do
        {
            alist.get(i);
            i++;
        }
        while (i < 4000);
    }

    public static void iteratehashmap(HashMap<Short, SomeClass> hmap)
    {
        Iterator<HashMap.Entry<Short, SomeClass>> hmapiterator = 
        map.entrySet().iterator();
        do
        {
            hmapiterator.next();
        }
        while (hmapiterator.hasNext());
    }   

    public static void iteratehashmapbykey(HashMap<Short, SomeClass> hmap)
    {
        short i = 0;
        do
        {
            hmap.get(i);
            i++;
        }
        while (i < 4000);
    }

    public static void getvaluebykey(HashMap<Short, SomeClass> hmap)
    {
        hmap.get(3999);
    }

    public static void getvaluebyindex(ArrayList<SomeClass> alist)
    {
        alist.get(3999);
    }
}

public class SomeClass
{
    int a = 0;
    float b = 0;
    short c = 0;

    public SomeClass()
    {
        a = (int)(Math.random() * 100000) + 1;
        b = (float)(Math.random() * 100000) + 1.0f;
        c = (short)((Math.random() * 32000) + 1);
    }
}

有趣的是,代码似乎分阶段预热。我确定的最后阶段是在所有方法的大约 120,000 次迭代之后。无论如何,在我的测试机器上(AMD x2-220,L3 + 1 个额外内核解锁,3.6 ghz,2.1 ghz NB),真正让我震惊的数字是最后两个报告的数字。即.get()最后一个条目ArrayList(index == 3999)所用的时间和.get()3999的短键关联的值所用的时间。

经过 2-3 个预热周期后,测试表明 ArrayList.get() 大约需要 56 ns,而 HashMap.get() 大约需要 68 ns。那是 。 . .不是我所期望的。我的HashMap 都被碰撞吃光了吗?所有的关键条目都应该自动装箱到 Shorts,它们应该报告它们存储的短值以响应.hashcode(),所以所有的哈希码都应该是唯一的。我想?

即使没有预热,ArrayList.get() 仍然更快。这与我在其他地方看到的一切相反,例如this question。当然,我还读到使用ListIterator 遍历ArrayList 比仅在循环中使用.get() 更快,显然情况并非如此。 . .

【问题讨论】:

  • 数组列表为什么会遍历列表?该元素位于给定索引处的数组中。在编写所有这些之前,您可以阅读 ArrayList 的代码来检查您的假设。
  • 是的,我知道这个名字会让人感到困惑。它是一个实现为数组数据结构的列表。也许您正在考虑一个链表数据结构,这是不同的。我记得我第一次看到它时也有点被甩了。
  • 另外,对于微观基准,使用 JMH 或 caliper。
  • 尝试不同的hashCode() 实现并比较结果。
  • @xdhmoore 我认为这是其中的一部分,尽管实际上我在这里表现出很多无知,无法理解 ArrayList 如何或何时提供 O(n) 性能以及 HashMap 在哪里将在相同的情况下提供 O(1) 性能。这是一个经常被引用的事实,我认为这是一个涉及许多不相关操作的全面声明是理所当然的。这是我的谬误。

标签: java performance arraylist hashmap


【解决方案1】:

ArrayList.get(index) 实际使用常量时间,因为ArrayList 由数组支持,所以它只使用后置数组中的索引。在最坏的情况下,ArrayList.contains(Object)O(n) 中的长时间操作。

【讨论】:

    【解决方案2】:

    ArrayList 必须遍历集合的每个元素才能达到其值

    这不是真的。 ArrayList 由一个数组支持,该数组允许恒定时间 get 操作。

    另一方面,HashMapget 首先必须对其参数进行哈希处理,然后必须遍历哈希码对应的存储桶,测试存储桶中的每个元素与给定键的相等性。这通常比仅仅索引一个数组要慢。

    【讨论】:

      【解决方案3】:

      ArrayListHashMap 都以数组为后盾,HashMap 必须计算键的哈希码,从中派生索引以用于访问数组,同时使用get你提供索引。所以它的 3 次操作对 ArrayList 的 1 次操作。

      但是 ListMap 是否支持数组是实现细节。因此,答案可能会因您使用的实现而异。

      【讨论】:

      • ArrayList 实现了RandomAccess,因此无论您使用哪种实现,您都可以确信get 将是恒定时间的。
      • @arshajii 是的,但不是List的每个实现
      【解决方案4】:

      HashMap 的大 O 是 O(1+α)。您的 α 来自哈希码冲突,必须遍历一个桶来检查是否相等。

      Big O 用于通过索引 O(1)ArrayList 中拉出项目

      有疑问时……画出来……

      【讨论】:

        【解决方案5】:

        哈希图在检索已知索引处的内容时并不快。如果您以已知顺序存储内容,则列表将胜出。

        但是,假设您没有将所有内容插入列表 1-4000,而是按照完全随机的顺序进行操作。现在要从列表中检索正确的项目,您必须一一检查每个项目以寻找正确的项目。但要从 hashmap 中检索它,您只需要知道插入时给它的键即可。

        真的,您应该将 Hashmap.get(i) 与

        进行比较
        for(Integer i : integerList)
           if(i==value)
               //found it!
        

        然后你就会看到 hashmap 的真正效率了。

        【讨论】:

        • 我添加了一个类 (SomeOtherClass),它返回 9999 的 .hashCode() 结果(作为 short),并在将单个实例放入HashMap。我将它固定在我的ArrayList 的索引 3999 处。当再次使用.hashCode() 重新生成密钥并“找到”我的HashMap 中的元素时,大约需要200 ns。搜索ArrayList(我将其视为未排序)花费了 8500ns。可以理解,这是元素被卡在最后的“最坏情况”场景,但它确实可以说明您的观点。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-22
        • 2021-05-31
        • 2018-10-17
        • 1970-01-01
        • 1970-01-01
        • 2013-07-02
        相关资源
        最近更新 更多