FindBugs警告:使用keySet迭代器而不是entrySet迭代器效率低下

时间:2012-09-28 11:36:12

标签: java performance findbugs

请参考以下方法:

public Set<LIMSGridCell> getCellsInColumn(String columnIndex){
    Map<String,LIMSGridCell> cellsMap = getCellsMap();
    Set<LIMSGridCell> cells = new HashSet<LIMSGridCell>();
    Set<String> keySet = cellsMap.keySet();
    for(String key: keySet){
      if(key.startsWith(columnIndex)){
        cells.add(cellsMap.get(key));
      }
    }
    return cells;
  }

FindBugs给出了这条警告信息:

  

低效使用keySet迭代器而不是entrySet迭代器   此方法使用的键访问Map条目的值   从keySet迭代器中检索。使用它更有效   地图的entrySet上的迭代器,以避免Map.get(键)   查找“。

5 个答案:

答案 0 :(得分:51)

您正在检索所有键(访问整个地图),然后对于某些键,您再次访问地图以获取值。

您可以遍历地图以获取地图条目(Map.Entry)(一对键和值)并仅访问地图一次。

Map.entrySet()提供了一组Map.Entry s,其中包含密钥和相应的值。

for ( Map.Entry< String, LIMSGridCell > entry : cellsMap.entrySet() ) {
    if ( entry.getKey().startsWith( columnIndex ) ) {
        cells.add( entry.getValue() );
    }
}

注意:我怀疑这会有很大的改进,因为如果你使用地图条目,你将为每个条目实例化一个对象。我不知道这是否真的比调用get()并直接检索所需的引用更快。

答案 1 :(得分:12)

如果某人仍然对详细且数字支持的答案感兴趣:是的,您应该使用entrySet()keySet(),以防您迭代整个地图。有关详细数字,请参阅this Gist。我使用JMH运行基准测试,以获得使用Oracle JDK8的Map的默认实现。

主要发现是:迭代keySet并重新查询每个键总是慢一点。一旦你有更大的地图,乘数就会变得很大(例如,对于ConcurrentSkipListMap,它总是5-10倍;而对于HashMap s,它不会超过2倍,高达一百万条目)。

然而,这些仍然是非常小的数字。迭代超过100万个条目的最慢方式是ConcurrentSkipListMap.keySet(),大约是500-700毫秒;虽然迭代超过IdentityHashMap.entrySet()只有25-30毫秒而LinkedHashMap.entrySet()仅落后于40-50毫秒(这并不奇怪,因为它内部有LinkedList,这有助于迭代)。作为上述链接Gist的概述:

Map type              | Access Type | Δ for 1M entries
----------------------+-------------+-----------------
HashMap               | .entrySet() |     69-72  ms
HashMap               |   .keySet() |     86-94  ms
ConcurrentHashMap     | .entrySet() |     72-76  ms
ConcurrentHashMap     |   .keySet() |     87-95  ms
TreeMap               | .entrySet() |    101-105 ms
TreeMap               |   .keySet() |    257-279 ms
LinkedHashMap         | .entrySet() |     37-49  ms
LinkedHashMap         |   .keySet() |     89-120 ms
ConcurrentSkipListMap | .entrySet() |     94-108 ms
ConcurrentSkipListMap |   .keySet() |    494-696 ms
IdentityHashMap       | .entrySet() |     26-29  ms
IdentityHashMap       |   .keySet() |     69-77  ms

所以底线是:它取决于你的用例。虽然迭代entrySet()肯定更快,但数字并不大,特别是对于相当小的地图。但是,如果您经常迭代一个包含100万个条目的Map,那么最好使用更快的方式;)

当然,数字只是为了相互比较,而不是绝对。

答案 2 :(得分:9)

您正在获取地图中的一组键,然后使用每个键从地图中获取值。

相反,您可以简单地遍历通过entrySet()返回给您的Map.Entry键/值对。这样你就可以避免相对昂贵的get()查询(注意在这里使用相对这个词)

e.g。

for (Map.Entry<String,LIMSGridCell> e : map.entrySet()) {
   // do something with...
   e.getKey();
   e.getValue();
}

答案 3 :(得分:2)

这是建议;不是你的问题的答案。 当您使用ConcurrentHashMap时;下面是javadoc

中提到的迭代器行为
  

视图的迭代器是一个永远不会的“弱一致”迭代器   抛出ConcurrentModificationException,并保证遍历   在构造迭代器时它们存在的元素,并且可能   (但不保证)反映后续的任何修改   构造

所以如果你使用EntrySet迭代器;这可能包含陈旧的键/值对;所以它会更好;从keySet iterator()获取密钥;并检查集合的价值。这将确保您从集合中获得最近的更改。

如果你对故障安全迭代器没问题;然后查看link;它声明使用entrySet;很少改善表现。

答案 4 :(得分:0)

在密钥集中,您需要获取所有密钥,然后搜索集合中的每个密钥。

此外,在entrySet上循环更快,因为您不会为每个键查询两次映射。

如果只需要键或只需要Map的值,那么请使用keySet()或values()。