web-dev-qa-db-ja.com

FindBugs警告:entrySetイテレーターの代わりにkeySetイテレーターの非効率的な使用

次の方法を参照してください。

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は次の警告メッセージを表示します。

"entrySetイテレータではなくkeySetイテレータの非効率的な使用このメソッドは、keySetイテレータから取得したキーを使用して、Mapエントリの値にアクセスします。entrySetでイテレータを使用する方が効率的ですMap.get(key)ルックアップを回避するためのマップ。」

30
Geek

すべてのキーを取得(マップ全体にアクセス)してから、一部のキーについては、マップに再度アクセスして値を取得します。

マップを反復処理してマップエントリ( Map.Entry )(キーと値の組み合わせ)を取得し、マップに1回だけアクセスできます。

Map.entrySet() は、それぞれがキーと対応する値を持つ_Map.Entry_ sのセットを提供します。

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

:マップエントリを使用する場合、各エントリに対してオブジェクトをインスタンス化するため、これが大幅に改善されるとは思いません。これがget()を呼び出して必要な参照を直接取得するよりも本当に速いかどうかはわかりません。

51
Matteo

誰かがまだ詳細で数字に裏打ちされた答えに興味があるなら:はい、あなたが全体を反復している場合にentrySet() vs. keySet()を使うべきですマップ。詳細な数字については this Gist をご覧ください。 Oracle JDK8を使用したMapのデフォルト実装のJMHでベンチマークを実行します。

主な発見は次のとおりです。keySetを反復処理し、すべてのキーを再クエリするのは常に少し遅くなります。マップが大きくなるとすぐに、乗数は非常に大きくなります(たとえば、ConcurrentSkipListMapの場合は常に5〜10倍になりますが、HashMapsの場合は最大で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万エントリのマップを非常に定期的に繰り返し処理する場合は、より高速な方法を使用することをお勧めします;)

もちろん、数字は絶対値ではなく、互いに比較するためのものです。

12
D. Kovács

マップ内のキーのセットを取得し、各キーを使用してマップから値を取得しています。

代わりに、entrySet()を介して返される Map.Entry キー/値のペアを単純に反復処理できます。そうすれば、比較的高価なget()ルックアップを避けることができます(ここではWordを相対的に使用していることに注意してください)

例えば.

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

これは提案です。本当にあなたの質問に対する答えではありません。 ConcurrentHashMapを使用している場合。以下は、javadocで言及されているイテレータの動作です。

ビューの反復子は、ConcurrentModificationExceptionをスローしない「弱整合性」反復子であり、反復子の構築時に存在した要素をトラバースすることを保証し、後続の変更を反映する可能性があります(が保証されません)建設へ。

したがって、EntrySetイテレーターを使用する場合;これには、古いキー/値ペアが含まれる場合があります。ので、より良いでしょう。 keySetからキーを取得iterator();コレクションの値を確認してください。これにより、コレクションから最新の変更を確実に取得できます。

フェイルセーフイテレータで問題ない場合;次に、これを確認します link ; entrySetを使用して記述します。パフォーマンスはほとんど向上しません。

2

キーセットでは、すべてのキーを取得してから、コレクション内のすべてのキーを検索する必要があります。

さらに、各キーに対してマップを2回クエリしないため、entrySetのループが高速になります。

Mapのキーまたは値のみが必要な場合は、代わりにkeySet()またはvalues()を使用します。

0
Chetan Laddha