【发布时间】:2011-04-21 16:02:14
【问题描述】:
全部,
谁能告诉我两者之间的性能问题到底是什么?该站点:CodeRanch 简要概述了使用 keySet() 和 get() 时所需的内部调用。但是,如果有人可以在使用 keySet() 和 get() 方法时提供有关流程的确切详细信息,那就太好了。这将帮助我更好地理解性能问题。
【问题讨论】:
标签: java performance collections
全部,
谁能告诉我两者之间的性能问题到底是什么?该站点:CodeRanch 简要概述了使用 keySet() 和 get() 时所需的内部调用。但是,如果有人可以在使用 keySet() 和 get() 方法时提供有关流程的确切详细信息,那就太好了。这将帮助我更好地理解性能问题。
【问题讨论】:
标签: java performance collections
使用 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() 之前检查对象的身份。
【讨论】:
get() 是 O(log(n)) 操作。
HashMap 中的 value 实现为二叉搜索树而不是 LinkedList。见openjdk.java.net/jeps/180
首先,这完全取决于您使用的地图类型。但是由于 JavaRanch 线程谈论 HashMap,我假设这就是您所指的实现。并且我们还假设您正在谈论来自 Sun/Oracle 的标准 API 实现。
其次,如果您在遍历哈希映射时担心性能问题,建议您查看LinkedHashMap。来自文档:
对 LinkedHashMap 的集合视图的迭代需要与地图大小成正比的时间,无论其容量如何。 HashMap 的迭代可能更昂贵,需要的时间与其容量成正比。
此实现的源代码可用。该实现基本上只是返回一个新的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 时会发生什么的代码。它遍历与地图容量一样长的整个数组。
在这里,您首先需要掌握一组键。这花费的时间与地图的 容量 成正比(与 LinkedHashMap 的 size 相对)。完成此操作后,您为每个键调用一次get()。当然,在一般情况下,通过良好的 hashCode 实现,这需要恒定的时间。但是,它不可避免地需要大量的.hashCode 和.equals 调用,这显然比仅仅调用entry.value() 需要更多的时间。
【讨论】:
Here is the link to an article 比较entrySet()、keySet() 和values() 的性能,以及何时使用每种方法的建议。
显然,只要您不需要Map.get() 的值,使用keySet() 比entrySet() 更快(除了更方便)。
【讨论】: