【问题标题】:Performance considerations for keySet() and entrySet() of MapMap 的 keySet() 和 entrySet() 的性能注意事项
【发布时间】:2011-04-21 16:02:14
【问题描述】:

全部,

谁能告诉我两者之间的性能问题到底是什么?该站点:CodeRanch 简要概述了使用 keySet() 和 get() 时所需的内部调用。但是,如果有人可以在使用 keySet() 和 get() 方法时提供有关流程的确切详细信息,那就太好了。这将帮助我更好地理解性能问题。

【问题讨论】:

    标签: java performance collections


    【解决方案1】:

    使用 entrySet 优于使用 keySet 的最常见情况是,当您遍历 Map 中的所有键/值对时。

    这样更有效率:

    for (Map.Entry entry : map.entrySet()) {
        Object key = entry.getKey();
        Object value = entry.getValue();
    }
    

    比:

    for (Object key : map.keySet()) {
        Object value = map.get(key);
    }
    

    因为在第二种情况下,对于 keySet 中的每个键,都会调用 map.get() 方法,这 - 在 HashMap 的情况下 - 要求键对象的 hashCode()equals() 方法在为了找到相关的值*。在第一种情况下,消除了额外的工作。

    编辑:如果您考虑 TreeMap,情况会更糟,其中 get 的调用是 O(log2(n)),即 will 的比较器可能需要运行 log2(n) 次(n = Map 的大小) 在找到相关值之前。

    *某些 Map 实现具有内部优化,可在调用 hashCode()equals() 之前检查对象的身份。

    【讨论】:

    • 另外,如果映射是 TreeMap 而不是 HashMap,get()O(log(n)) 操作。
    • @ILMIian 和 Michael:为什么 TreeMap 和 HashMap 有区别?
    • TreeMap 和 HashMap 是不同的数据结构,TreeMap 基于红/黑树。 HashMap 是一个桶和列表哈希表。在这两种情况下,对 get() 的调用都不是免费的,其成本取决于数据结构的类型。
    • Java 8 ( & above ) 在 HashMap 中的 value 实现为二叉搜索树而不是 LinkedList。见openjdk.java.net/jeps/180
    【解决方案2】:

    首先,这完全取决于您使用的地图类型。但是由于 JavaRanch 线程谈论 HashMap,我假设这就是您所指的实现。并且我们还假设您正在谈论来自 Sun/Oracle 的标准 API 实现。

    其次,如果您在遍历哈希映射时担心性能问题,建议您查看LinkedHashMap。来自文档:

    对 LinkedHashMap 的集合视图的迭代需要与地图大小成正比的时间,无论其容量如何。 HashMap 的迭代可能更昂贵,需要的时间与其容量成正比。

    HashMap.entrySet()

    此实现的源代码可用。该实现基本上只是返回一个新的HashMap.EntrySet。一个看起来像这样的类:

    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator(); // returns a HashIterator...
        }
        // ...
    }
    

    HashIterator 看起来像

    private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;    // next entry to return
        int expectedModCount;   // For fast-fail
        int index;      // current slot
        Entry<K,V> current; // current entry
    
        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }
    
        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;
        }
    
        // ...
    }
    

    所以你有它......这就是当你迭代一个 entrySet 时会发生什么的代码。它遍历与地图容量一样长的整个数组。

    HashMap.keySet() 和 .get()

    在这里,您首先需要掌握一组键。这花费的时间与地图的 容量 成正比(与 LinkedHashMap 的 size 相对)。完成此操作后,您为每个键调用一次get()。当然,在一般情况下,通过良好的 hashCode 实现,这需要恒定的时间。但是,它不可避免地需要大量的.hashCode.equals 调用,这显然比仅仅调用entry.value() 需要更多的时间。

    【讨论】:

    • +1 "迭代 LinkedHashMap 的集合视图所需的时间与映射的大小成正比,无论其容量如何。迭代 HashMap 可能更昂贵,所需的时间与它的容量。”
    • 但是如果你只需要访问键或者只需要访问 Map 的值,那么更喜欢迭代 keySet() 返回的 Set 和 Collection 返回的 values()。还有一点,keySet() 返回的 Set 和 values() 返回的 Collection 都由原始 Map 支持。也就是说,如果您对它们进行任何修改,它们将反映在 Map 中,但是,它们都不支持 add() 和 addAll() 方法,即您不能将新键添加到 Set 或新值在集合中。
    • @aioobe 正如您所写的那样,“这就是指示当您遍历 entrySet 时会发生什么的代码。它遍历整个数组,与地图的容量一样长。”不应该是“......和地图一样长”吗?
    • 很好很好的答案。我总是更喜欢参考源代码,因为它是最终的真相来源
    【解决方案3】:

    Here is the link to an article 比较entrySet()keySet()values() 的性能,以及何时使用每种方法的建议。

    显然,只要您不需要Map.get() 的值,使用keySet()entrySet() 更快(除了更方便)。

    【讨论】:

    • 您在那篇文章中说过“这种方法(使用 keySet 或 values 代替 entrySet)与 entrySet 迭代相比具有轻微的性能优势(大约快 10%)并且更干净。”我可以知道你是如何得到“10%”的价值的吗?您没有显示任何测量值,也没有显示任何包含此类值的外部数据。
    • @dantuch 我不是 Sergiy,但这篇文章有道理,恕我直言。不过这篇文章很旧,从 2008 年开始。你总是可以使用谷歌的 Caliper 创建一个微基准,例如如果您对最新的 JDK 感到好奇,请发布结果。
    猜你喜欢
    • 1970-01-01
    • 2012-02-03
    • 2011-07-09
    相关资源
    最近更新 更多