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

请参考以下方法:

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

FindBugs给出了这个警告信息:

低效使用keySet迭代器而不是entrySet迭代器此方法使用从keySet迭代器检索的键访问Map条目的值。在map的entrySet上使用迭代器更有效,以避免Map .get(key)lookup。“

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

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

Map.entrySet()提供一组Map.Entry每个Map.Entry都带有键和相应的值。

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

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

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

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

例如

 for (Map.Entry e : map.entrySet()) { // do something with... e.getKey(); e.getValue(); } 

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

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

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

 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,那么最好使用更快的方式;)

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

这是建议; 不是你的问题的答案。 当您使用ConcurrentHashMap时; 下面是javadoc中提到的迭代器行为

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

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

如果你对故障安全迭代器没问题; 然后查看此链接 ; 它使用entrySet表示; 几乎没有提高性能。

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

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

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