web-dev-qa-db-fra.com

Avertissement FindBugs: utilisation inefficace de l'itérateur keySet au lieu de l'itérateur setSet

Veuillez vous référer à la méthode suivante:

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;
  }

Les FindBugs donnent ce message d'avertissement:

"tilisation inefficace de l'itérateur keySet au lieu de l'itérateur entrySet Cette méthode accède à la valeur d'une entrée de mappage, en utilisant une clé qui a été récupérée à partir d'un itérateur keySet. Il est plus efficace d'utiliser un itérateur sur l'entréeSet de la carte, pour éviter la recherche Map.get (clé). "

30
Geek

Vous récupérez toutes les clés (accédez à toute la carte), puis pour certaines clés, vous accédez à nouveau à la carte pour obtenir la valeur.

Vous pouvez parcourir la carte pour obtenir des entrées de carte ( Map.Entry ) (couples de clés et de valeurs) et accéder à la carte une seule fois.

Map.entrySet () fournit un ensemble de Map.Entry Chacun avec la clé et la valeur correspondante.

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

Remarque : Je doute que ce sera une grande amélioration car si vous utilisez des entrées de carte, vous instancierez un objet pour chaque entrée. Je ne sais pas si c'est vraiment plus rapide que d'appeler get() et de récupérer directement la référence nécessaire.

51
Matteo

Si quelqu'un est toujours intéressé par une réponse détaillée et numérotée: oui, vous devez utiliser entrySet() vs keySet() au cas où vous êtes itérant sur l'ensemble Carte. Voir ce Gist pour les chiffres détaillés. Je lance un benchmark avec JMH pour les implémentations par défaut de Map avec Oracle JDK8.

Le résultat principal est: il est toujours un peu plus lent d'itérer sur le keySet et de réinterroger pour chaque clé. Dès que vous avez de plus grandes cartes, le multiplicateur peut devenir assez grand (par exemple, pour un ConcurrentSkipListMap il est toujours 5-10x; tandis que pour HashMaps il n'est pas plus grand que 2x pour jusqu'à un millions d'entrées).

Cependant, ce sont encore de très petits nombres. La manière la plus lente d'itérer plus d'un million d'entrées est d'utiliser une ConcurrentSkipListMap.keySet(), qui est d'environ 500 à 700 millisecondes; tout en itérant sur IdentityHashMap.entrySet() n'est que de 25 à 30 millisecondes avec LinkedHashMap.entrySet() juste derrière avec 40 à 50 millisecondes (pas surprenant, car il a un LinkedList à l'intérieur, ce qui aide à l'itération). Comme un aperçu du Gist lié ci-dessus:

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

Donc, l'essentiel est: cela dépend de votre cas d'utilisation. Bien qu'il soit certainement plus rapide d'itérer sur la entrySet() les chiffres ne sont pas énormes, en particulier pour les cartes raisonnablement petites. Cependant, si vous parcourez régulièrement une carte avec 1 million d'entrées, mieux vaut utiliser la méthode la plus rapide;)

Les chiffres ne sont bien sûr que pour comparer les uns avec les autres, pas absolus.

12
D. Kovács

Vous obtenez le jeu de clés sur la carte, puis utilisez chaque clé pour extraire la valeur de la carte.

Au lieu de cela, vous pouvez simplement parcourir les paires clé/valeur Map.Entry qui vous sont retournées via entrySet(). De cette façon, vous évitez la recherche relativement coûteuse de get() (notez l'utilisation du mot relativement ici)

par exemple.

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

Telle est la suggestion; pas vraiment une réponse à votre question. Lorsque vous travaillez avec ConcurrentHashMap; ci-dessous est le comportement de l'itérateur mentionné dans javadoc

L'itérateur de la vue est un itérateur "faiblement cohérent" qui ne lèvera jamais ConcurrentModificationException, et garantit de parcourir les éléments tels qu'ils existaient lors de la construction de l'itérateur, et peut ( mais n'est pas garanti de ) reflète toutes les modifications ultérieures à la construction.

Donc, si vous utilisez l'itérateur EntrySet; cela peut contenir une paire clé/valeur périmée; ce serait donc mieux; obtenir la clé de l'itérateur keySet (); et vérifiez auprès de la collection pour la valeur. cela garantira que vous obtenez la modification récente de la collection.

Si vous êtes d'accord avec l'itérateur à sécurité intégrée; puis vérifiez cela lien ; il indique en utilisant entrySet; peu améliorer les performances.

2
Kanagavelu Sugumar

Dans le jeu de clés, vous devez obtenir toutes les clés, puis rechercher chaque clé de la collection.

De plus, la boucle sur le entrySet est plus rapide, car vous n'interrogez pas la carte deux fois pour chaque clé.

Si vous n'avez besoin que de clés ou de valeurs de votre carte, utilisez plutôt keySet () ou values ​​().

0
Chetan Laddha