Map的keySet()和entrySet()的性能注意事项


76

所有,

谁能让我确切知道两者之间的性能问题?站点:CodeRanch简要概述了使用keySet()和get()时需要的内部调用。但是如果使用keySet()和get()方法时任何人都可以提供有关流的确切详细信息,那将是很好的。这将帮助我更好地了解性能问题。

Answers:


70

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

其次,如果您在遍历哈希映射时担心性能,我建议您看一下LinkedHashMap。从文档:

在LinkedHashMap的集合视图上进行迭代需要的时间与地图的大小成正比,而不管其容量如何。在HashMap上进行迭代可能会更昂贵,需要的时间与其容量成正比。

HashMap.entrySet()

此实现的源代码可用。实现基本上只是返回一个new 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的大小)。完成此操作后,您需要get()为每个键调用一次。当然,在一般情况下,具有良好的hashCode实现会花费恒定的时间。但是,这不可避免地需要大量的.hashCode.equals呼叫,这显然会比仅进行entry.value()呼叫花费更多的时间。


1
+1“在LinkedHashMap的集合视图上进行迭代需要的时间与地图的大小成正比,而不论其容量如何。在HashMap上进行迭代的成本可能更高,而与其容量成比例的时间也很可能。”
metdos 2012年

但是,如果您只需要访问键或只需要访问Map的值,则优先遍历keySet()返回的Set和Collection返回的values()。还有一点,由keySet()返回的Set和由values()返回的Collection都由原始Map支持。也就是说,如果您对它们进行了任何修改,它们都会反映在地图中,但是,它们都不支持add()和addAll()方法,即您不能将新键添加到Set或新值中在集合中。
sactiw 2014年

@aioobe AS,您已经编写了“那段代码,指示当您遍历entrySet时将发生什么。它遍历整个数组,该数组与地图的容量一样长。” 它不应该是“ .....与地图大小一样长”吗?
Sumit Kumar Saha

好好答案。我总是喜欢参考源代码,因为它是真理的最终来源
ACV

75

使用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()andequals()方法进行求值以便找到关联的值*。在第一种情况下,省去了额外的工作。

编辑:如果您考虑使用TreeMap,这会变得更糟,其中对get的调用为O(log2(n)),即,查找的比较器可能需要运行log2(n)次(n = Map的大小)才能找到相关值。

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


3
另外,如果地图是TreeMap而不是HashMap,get()则是O(log(n))操作。
ILMTitan

@ILMIian和Michael:为什么TreeMap和HashMap有区别?
name_masked 2010年

TreeMap和HashMap是不同的数据结构,TreeMap基于红色/黑色树。HashMap是存储区和列表哈希表。在这两种情况下,对get()的调用都不是免费的,其成本取决于数据结构的类型。
Michael Barker

1
爪哇8(上面)具有HashMap作为二进制搜索树而不是链表实现。参见openjdk.java.net/jeps/180
新手用户

14

这里是指向entrySet()keySet()和的性能进行比较的文章的链接values(),以及有关何时使用每种方法的建议。

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


1
您在那篇文章中说:“此方法(使用keySet或值而不是entrySet)与entrySet迭代相比,性能略有优势(快了约10%),并且更干净。” 我可以知道您是如何获得“ 10%”的值的吗?您没有显示任何测量值,也没有显示任何包含该值的外部数据。
dantuch

@dantuch我不是Sergiy,但是这篇文章很有意思,恕我直言。不过,该文章年代久远,始于2008年。您随时可以使用Google的Caliper创建微基准测试,例如,对于最新的JDK,如果您感到好奇,请发布结果。
Stefan L'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.