Pourquoi list.size ()> 0 est-il plus lent que list.isEmpty () en Java?

Pourquoi list.size()>0 plus lent que list.isEmpty() en Java? En d’autres termes, pourquoi isEmpty() est préférable à la size()>0 ?

Lorsque je regarde l’implémentation dans ArrayList , il semble que la vitesse soit la même:

ArrayList.size ()

  /** * Returns the number of elements in this list. * * @return the number of elements in this list */ public int size() { return size; } 

ArrayList.isEmpty ()

  /** * Returns true if this list contains no elements. * * @return true if this list contains no elements */ public boolean isEmpty() { return size == 0; } 

Si nous écrivons simplement un programme simple pour obtenir le temps nécessaire aux deux méthodes, cette size() prendra plus isEmpty() dans tous les cas, pourquoi?

Voici mon TestCode;

 import java.util.List; import java.util.Vector; public class Main { public static void main(Ssortingng[] args) { List l=new Vector(); int i=0; for(i=0;i<10000;i++){ l.add(new Integer(i).toString()); } System.out.println(i); Long sTime=System.nanoTime(); l.size(); Long eTime=System.nanoTime(); l.isEmpty(); Long eeTime=System.nanoTime(); System.out.println(eTime-sTime); System.out.println(eeTime-eTime); } } 

Ici, eTime-sTime>eeTime-eTime dans tous les cas. Pourquoi?

Votre code de test est défectueux.

Il suffit d’inverser l’ordre, c’est-à-dire d’appeler en premier et de prendre la taille> 0 seconde pour obtenir le résultat inverse . Cela est dû au chargement de la classe, à la mise en cache, etc.

Pour ArrayList s, oui – vous avez raison de dire que les opérations prennent (à peu près) le même temps.

Par exemple, pour les autres implémentations de listes chaînées * naïves de listes, le comptage de la taille peut prendre beaucoup de temps, alors que vous ne vous souciez que de la valeur supérieure à zéro.

Donc, si vous savez absolument que la liste est une implémentation de ArrayList et qu’elle ne changera jamais, cela n’a pas vraiment d’importance. mais:

  1. C’est une mauvaise pratique de programmation de toute façon pour s’attacher à une implémentation spécifique.
  2. Si les choses changent quelques années plus tard avec la restructuration du code, les tests montreront que “cela fonctionne” mais que les choses tournent moins efficacement qu’auparavant
  3. Même dans le meilleur des cas, size() == 0 n’est toujours pas plus rapide que isEmpty() , il n’y a donc aucune raison impérieuse d’utiliser le premier.
  4. isEmpty est une définition plus claire de ce dont vous vous souciez réellement et que vous testez, ce qui rend votre code un peu plus compréhensible.

(En outre, je réviserais l’utilisation de NULL dans le titre de votre question; la question elle-même et ces opérations n’ont rien à voir avec la question de savoir si des références à un object sont nulles.)

* J’ai initialement écrit LinkedList ici, faisant référence implicitement à java.util.LinkedList, bien que cette implémentation particulière stocke explicitement sa longueur, faisant ici de la taille () une opération O (1). Une opération de liste liée plus naïve pourrait ne pas le faire, et au sens plus général, il n’y a pas de garantie d’efficacité sur les implémentations de List.

Je suis désolé, mais votre benchmark est défectueux. Jetez un coup d’œil à la théorie et à la pratique de Java: Anatomie d’un microbenchmark défectueux pour une description générale de l’approche des benchmarks.


Mise à jour : pour un benchmark approprié, vous devriez regarder dans Japex .

Tu as dit:

Ici eTime-sTime>eeTime-eTime dans tous les cas Pourquoi?

Tout d’abord, c’est probablement à cause de votre code de test. Vous ne pouvez pas tester la vitesse d’appel de l.size () et de l.isEmpty () en même temps, car ils interrogent tous deux la même valeur. L’appel de l.size () a probablement chargé la taille de votre liste dans votre cache cpu et l’appel de l.isEmpty () est beaucoup plus rapide.

Vous pourriez essayer d’appeler l.size () quelques millions de fois et l.isEmpty () quelques millions de fois dans deux programmes distincts, mais en théorie, le compilateur pourrait simplement optimiser tous ces appels car vous ne faites rien avec Les resultats.

Dans tous les cas, la différence de performance entre les deux sera négligeable, en particulier une fois que vous faites la comparaison nécessaire pour voir si la liste est vide ( l.size() == 0 ). Très probablement, le code généré sera presque complètement similaire. Comme d’autres affiches l’ont noté, vous voulez optimiser la lisibilité dans ce cas, pas la rapidité.

edit: je l’ai testé moi-même. C’est à peu près tout. size() et isEmpty() utilisés sur Vector donné des résultats différents sur les longues séries, ni sur les autres. Lorsqu’il est exécuté sur une size() ArrayList size() semblait plus rapide, mais pas beaucoup. Cela est probablement dû au fait que l’access à Vector est synchronisé. Par conséquent, ce que vous constatez réellement lorsque vous essayez d’évaluer les access à ces méthodes, c’est la surcharge de synchronisation, qui peut être très sensible.

La chose à retenir ici est que lorsque vous essayez d’optimiser un appel de méthode avec une différence de temps d’exécution de quelques nanosecondes, vous le faites mal . Commencez par obtenir les bases, comme si vous utilisiez Long s où vous devriez utiliser long .

.size () doit regarder la liste entière, tandis que .isEmpty () peut s’arrêter au premier.

Évidemment, dépend de la mise en œuvre, mais comme cela a été dit précédemment, si vous n’avez pas besoin de connaître la taille réelle, pourquoi en dériver tous les éléments?

Compte tenu de ces deux implémentations, la vitesse devrait être la même, c’est vrai.

Mais ce ne sont pas les seules implémentations possibles pour ces méthodes. Une liste de liens primitifs (qui ne stocke pas la taille séparément), par exemple, pourrait répondre à isEmpty() beaucoup plus rapidement qu’un appel de size() .

Plus important encore: isEmpty() décrit exactement votre intention, alors que size()==0 est inutilement complexe (pas très complexe, bien sûr, mais toute complexité inutile doit être évitée).

Le comptage des éléments dans une liste chaînée peut être très lent.

Selon PMD (parsingur de code source Java basé sur des règles statiques), isEmpty () est préférable. Vous pouvez trouver le jeu de règles PMD ici. Recherchez la règle “UseCollectionIsEmpty”.

http://pmd.sourceforge.net/rules/design.html

Selon moi, cela aide aussi à garder le code source entier cohérent plutôt que la moitié des gens utilisant isEmpty () et le rest en utilisant size () == 0.

Il est impossible de dire en général ce qui est plus rapide, car cela dépend de l’implémentation de la List interface que vous utilisez.

Supposons que nous parlions de ArrayList . Recherchez le code source de ArrayList , vous pouvez le trouver dans le fichier src.zip de votre répertoire d’installation JDK. Le code source des méthodes isEmpty et la size se présentent comme suit (Sun JDK 1.6 update 16 pour Windows):

 public boolean isEmpty() { return size == 0; } public int size() { return size; } 

Vous pouvez facilement voir que les deux expressions isEmpty() et size() == 0 seront exactement les mêmes, donc l’une n’est certainement pas plus rapide que l’autre.

Si vous êtes intéressé par son fonctionnement pour d’autres implémentations de la List interfaces, recherchez vous-même le code source et découvrez-le.