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

Gee*_*eek 30 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;
  }
Run Code Online (Sandbox Code Playgroud)

FindBugs给出了这个警告信息:

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

Mat*_*teo 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() );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 但是对hashMap O(1)不是get()吗? (4认同)
  • @Geek:是的.请参阅我添加的说明.我怀疑FindBugs的建议真的有意义.实例化和get()都是O(1) (4认同)
  • 地图可以存储Entry(例如Sun的HashMap实现),因此不需要实例化.get()可能超过O(1),例如TreeMap或具有错误哈希函数的HashMap.但你是对的,在大多数情况下它不会产生显着的差异. (4认同)

D. *_*ács 12

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

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

但是,这些仍然是非常小的数字.迭代超过100万条目的最慢方式是a 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
Run Code Online (Sandbox Code Playgroud)

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

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


Bri*_*new 9

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

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

例如

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

  • 但是他没有通过get()访问_each_值.仅使用其条件与条件匹配的那些.我认为没有一般规则可以选择哪种方式.它取决于匹配条件的键的分数.显然,FindBugs无法检查. (5认同)
  • O(1)没有规定需要多长时间,只是它是常数 (2认同)