【问题标题】:HashMap vs LinkedHashMap performance in iteration over values()HashMap 与 LinkedHashMap 在值迭代中的性能()
【发布时间】:2012-10-11 12:05:05
【问题描述】:

HashMapLinkedHashMap 在通过values() 函数遍历时有什么性能差异吗?

【问题讨论】:

    标签: java collections hashmap linkedhashmap


    【解决方案1】:

    我认为LinkedHashMap 的遍历速度必须更快,因为其nextEntry 在其Iterator 中实现了卓越的nextEntry

    原因如下:

    让我们从values 实现一步一步来。
    valuesHashMap 实现是这样的:

    public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }
    

    LinkedHashMap 扩展自 HashMap 并继承了相同的实现。

    两者的区别在于IteratorValues 实现。

    对于HashMap,它从java.util.HashMap.HashIterator延伸

    private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }
    

    但对于LinkedHashMap,它从java.util.LinkedHashMap.LinkedHashIterator 延伸

    private class ValueIterator extends LinkedHashIterator<V> {
        public V next() { return nextEntry().value; }
    }
    

    所以差异基本上归结为nextEntry实现。

    对于 LinkedHashMap 它只是调用 e.after 其中 e 是 Entry , 但是对于HashMap,遍历Entry[] 数组以查找下一个下一个涉及一些工作。

    更新nextEntry()HashMap 中的代码

    final Entry<K,V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        Entry<K,V> e = next;
        if (e == null)
            throw new NoSuchElementException();
    
        if ((next = e.next) == null) {
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
        current = e;
        return e;
    }
    

    Entry[] 不是连续存储。 (两者之间可能有空值)。如果你看一下上面的代码,它的作用是指向当前的 next 并通过遍历 Entry[] 找到下一个。

    但是我认为这种性能提升是以插入为代价的。查看两个类中的addEntry 方法作为练习。

    【讨论】:

    • 能否请您详细说明或编辑此短语"but for HashMap there is some work involved in traversing the Entry[] array to find the next next."
    【解决方案2】:

    我编写了一个小分析程序,创建 100 万个键(整数)与 Boolean.TRUE,重复 100 次。发现以下内容:

    HashMap:-
    Create:  3.7sec
    Iterate: 1.1sec
    Access:  1.5sec
    Total:   6.2sec
    
    LinkedHashMap:-
    Create:  4.7sec   (30% slower)
    Iterate: 0.5sec   (50% faster)
    Access:  0.8sec   (50% faster)
    Total :  6.0sec
    

    垃圾收集没有这样做会在一定程度上污染数字,但是我认为 LinkedHashMap 比 HashMap 更有优势,我将在以后的代码中使用它。

    【讨论】:

    • 对于迭代,特别是考虑到上述 cmets,这很有意义,但我不清楚使用 LinkedHashMap 访问速度如何更快。 LinkedHashMapget(Object key) 函数本质上与 HashMapget(Object key) 函数做同样的事情,而且在返回结果之前检查 afterNodeAccess() 函数,这对我来说毫无意义。
    • 我不认为你的测试是正确的并且没有正确完成
    • 使用LinkedHashMap 将某个任务从25 秒加速到8 秒!我为插入的每个条目迭代整个映射(以进行一些特定于值的关系修复),所以 O(N^2)。 (86M nextNode() 调用,哈希表大小为 1M 和 ~13K 元素)
    【解决方案3】:

    几乎没有关系。问题是:你需要什么。如果元素的顺序相关,则必须使用LinkedHashMap。否则你就不需要它,所以使用HashMap

    【讨论】:

      【解决方案4】:

      我在 UnitTest 中尝试过,迭代 values() 10000 次,毫秒:806 与 902。几乎相同。

      【讨论】:

      • 相差 11%。这很重要。
      【解决方案5】:

      是的,在 HashMapLinkedHashMap 的所有迭代中,性能差异将相同:HashMap 所花费的时间与条目数加上哈希表的大小成正比,@987654324 @ 只需要与条目数成正比的时间。

      【讨论】:

        【解决方案6】:

        最好的建议是“不要害怕尝试”,但我很确定它们非常相似。值集的 getter 是 O(1),每个迭代器步骤也是如此。遍历链表与遍历哈希桶一样简单,但链表的优势可能很小。

        【讨论】:

          【解决方案7】:

          代码...

          import java.lang.management.GarbageCollectorMXBean;
          import java.lang.management.ManagementFactory;
          import java.util.HashMap;
          import java.util.HashSet;
          import java.util.LinkedHashMap;
          import java.util.Map;
          
          public class MapTest
          {
              public static void main(String[] args)
              {
                  int iterations = 1000000;
          
                  long time1, time2, time3;
                  System.nanoTime(); //Just to make sure any possible overhead is done...though there shouldn't really be any
          
                  int sequential[] = new int[iterations]; //Counting up from 0
                  int random[] = new int[iterations]; //Same set of values, but randomized (no duplicates)
                  HashSet<Integer> addedRandoms = new HashSet<>();
                  for (int i = 0; i < iterations; i++)
                  {
                      sequential[i] = i;
          
                      int randomVal = random(iterations);
                      while (addedRandoms.contains(randomVal)) randomVal = random(iterations); //Get another random instead of sequentially finding another unused value, to prevent clumping
                      addedRandoms.add(randomVal);
                      random[i] = random(iterations);
                  }
          
                  HashMap<Integer, Integer> hashMap = new HashMap<>();
                  LinkedHashMap<Integer, Integer> linkedHashMap = new LinkedHashMap<>();
          
          
                  int gcRuns = 0, prevGCRuns;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) gcRuns += gcBean.getCollectionCount();
                  prevGCRuns = gcRuns;
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : sequential) hashMap.put(i, 0);
                  time2 = System.nanoTime();
                  for (int i : sequential) linkedHashMap.put(i, 0);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Put: sequential key (from 0 to " + (iterations - 1) + "), no overwrites", time1, time2, time3, prevGCRuns);
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : random) hashMap.put(i, 0);
                  time2 = System.nanoTime();
                  for (int i : random) linkedHashMap.put(i, 0);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Put: random key (between 0 and " + (iterations - 1) + " inclusive), all overwrites (exactly one per entry, random order)", time1, time2, time3, prevGCRuns);
          
          
                  //Attempt GC
                  System.gc();
                  prevGCRuns = 0;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) prevGCRuns += gcBean.getCollectionCount();
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : sequential) hashMap.put(i, 0);
                  time2 = System.nanoTime();
                  for (int i : sequential) linkedHashMap.put(i, 0);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Put: sequential key (from 0 to " + (iterations - 1) + "), all overwrites (exactly one per entry, sequential order)", time1, time2, time3, prevGCRuns);
          
          
                  //Empty maps
                  hashMap = new HashMap<>();
                  linkedHashMap = new LinkedHashMap<>();
          
          
                  //Attempt GC
                  System.gc();
                  prevGCRuns = 0;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) prevGCRuns += gcBean.getCollectionCount();
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : random) hashMap.put(i, 0);
                  time2 = System.nanoTime();
                  for (int i : random) linkedHashMap.put(i, 0);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Put: random key (between 0 and " + (iterations - 1) + " inclusive), no overwrites", time1, time2, time3, prevGCRuns);
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : sequential) hashMap.get(i);
                  time2 = System.nanoTime();
                  for (int i : sequential) linkedHashMap.get(i);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Sequential get, randomized internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : random) hashMap.get(i);
                  time2 = System.nanoTime();
                  for (int i : random) linkedHashMap.get(i);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Random get, randomized internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Set sequential keys
                  hashMap = new HashMap<>();
                  linkedHashMap = new LinkedHashMap<>();
                  for (int i : sequential)
                  {
                      hashMap.put(i, 0);
                      linkedHashMap.put(i, 0);
                  }
          
          
                  //Attempt GC
                  System.gc();
                  prevGCRuns = 0;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) prevGCRuns += gcBean.getCollectionCount();
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : sequential) hashMap.get(i);
                  time2 = System.nanoTime();
                  for (int i : sequential) linkedHashMap.get(i);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Sequential get, sequential internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : random) hashMap.get(i);
                  time2 = System.nanoTime();
                  for (int i : random) linkedHashMap.get(i);
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("Random get, sequential internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : hashMap.values()) ;
                  time2 = System.nanoTime();
                  for (int i : linkedHashMap.values()) ;
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("values() iteration, sequential internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Set random keys
                  hashMap = new HashMap<>();
                  linkedHashMap = new LinkedHashMap<>();
                  for (int i : random)
                  {
                      hashMap.put(i, 0);
                      linkedHashMap.put(i, 0);
                  }
          
          
                  //Attempt GC
                  System.gc();
                  prevGCRuns = 0;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) prevGCRuns += gcBean.getCollectionCount();
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : hashMap.values()) ;
                  time2 = System.nanoTime();
                  for (int i : linkedHashMap.values()) ;
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("values() iteration, randomized internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : hashMap.keySet()) ;
                  time2 = System.nanoTime();
                  for (int i : linkedHashMap.keySet()) ;
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("keySet() iteration, randomized internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Set sequential keys
                  hashMap = new HashMap<>();
                  linkedHashMap = new LinkedHashMap<>();
                  for (int i : sequential)
                  {
                      hashMap.put(i, 0);
                      linkedHashMap.put(i, 0);
                  }
          
          
                  //Attempt GC
                  System.gc();
                  prevGCRuns = 0;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) prevGCRuns += gcBean.getCollectionCount();
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (int i : hashMap.keySet()) ;
                  time2 = System.nanoTime();
                  for (int i : linkedHashMap.keySet()) ;
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("keySet() iteration, sequential internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) ;
                  time2 = System.nanoTime();
                  for (Map.Entry<Integer, Integer> entry : linkedHashMap.entrySet()) ;
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("entrySet() iteration, sequential internal keys", time1, time2, time3, prevGCRuns);
          
          
                  //Set random keys
                  hashMap = new HashMap<>();
                  linkedHashMap = new LinkedHashMap<>();
                  for (int i : random)
                  {
                      hashMap.put(i, 0);
                      linkedHashMap.put(i, 0);
                  }
          
          
                  //Attempt GC
                  System.gc();
                  prevGCRuns = 0;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) prevGCRuns += gcBean.getCollectionCount();
          
          
                  //Test
                  time1 = System.nanoTime();
                  for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) ;
                  time2 = System.nanoTime();
                  for (Map.Entry<Integer, Integer> entry : linkedHashMap.entrySet()) ;
                  time3 = System.nanoTime();
          
                  prevGCRuns = printAndReset("entrySet() iteration, randomized internal keys", time1, time2, time3, prevGCRuns);
              }
          
          
              protected static int printAndReset(String description, long time1, long time2, long time3, int prevGCRuns)
              {
                  System.out.println(description);
                  System.out.println("HashMap: " + (time2 - time1) + " nanos");
                  System.out.println("LinkedHashMap: " + (time3 - time2) + " nanos");
                  int gcRuns = 0;
                  for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) gcRuns += gcBean.getCollectionCount();
                  System.out.println("GC during test: " + (gcRuns != prevGCRuns));
                  System.out.println();
          
                  return gcRuns;
              }
          
          
              public static int random(int maxvalue)
              {
                  return (int) ((double) maxvalue * Math.random());
              }
          }
          

          输出...

          Put: sequential key (from 0 to 999999), no overwrites
          HashMap: 30190070 nanos
          LinkedHashMap: 44618672 nanos
          GC during test: false
          
          Put: random key (between 0 and 999999 inclusive), all overwrites (exactly one per entry, random order)
          HashMap: 118536111 nanos
          LinkedHashMap: 110828524 nanos
          GC during test: false
          
          Put: sequential key (from 0 to 999999), all overwrites (exactly one per entry, sequential order)
          HashMap: 25070771 nanos
          LinkedHashMap: 23569874 nanos
          GC during test: false
          
          Put: random key (between 0 and 999999 inclusive), no overwrites
          HashMap: 93353708 nanos
          LinkedHashMap: 106686445 nanos
          GC during test: false
          
          Sequential get, randomized internal keys
          HashMap: 38817600 nanos
          LinkedHashMap: 39499373 nanos
          GC during test: false
          
          Random get, randomized internal keys
          HashMap: 42636179 nanos
          LinkedHashMap: 51062653 nanos
          GC during test: false
          
          Sequential get, sequential internal keys
          HashMap: 19986540 nanos
          LinkedHashMap: 19502021 nanos
          GC during test: false
          
          Random get, sequential internal keys
          HashMap: 58083205 nanos
          LinkedHashMap: 59547574 nanos
          GC during test: false
          
          values() iteration, sequential internal keys
          HashMap: 21284921 nanos
          LinkedHashMap: 18383069 nanos
          GC during test: false
          
          values() iteration, randomized internal keys
          HashMap: 19616868 nanos
          LinkedHashMap: 15392964 nanos
          GC during test: false
          
          keySet() iteration, randomized internal keys
          HashMap: 18054895 nanos
          LinkedHashMap: 16067725 nanos
          GC during test: false
          
          keySet() iteration, sequential internal keys
          HashMap: 18764430 nanos
          LinkedHashMap: 18604873 nanos
          GC during test: false
          
          entrySet() iteration, sequential internal keys
          HashMap: 18493825 nanos
          LinkedHashMap: 18067752 nanos
          GC during test: false
          
          entrySet() iteration, randomized internal keys
          HashMap: 16252707 nanos
          LinkedHashMap: 13175517 nanos
          GC during test: false
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-05-13
            • 2014-12-06
            • 2013-07-28
            • 2015-06-05
            • 1970-01-01
            • 1970-01-01
            • 2015-10-31
            • 1970-01-01
            相关资源
            最近更新 更多