web-dev-qa-db-fra.com

Hashset vs Treeset

J'ai toujours aimé les arbres, que Nice O(n*log(n)) et leur ordre. Cependant, tous les ingénieurs en logiciel que j'ai connus m'ont demandé de façon explicite pourquoi j'utiliserais un TreeSet. En tant que CS, je ne pense pas que ce que vous utilisez importe peu, et je me moque bien de me mêler des fonctions de hachage et des compartiments (dans le cas de Java).

Dans quels cas devrais-je utiliser un HashSet sur un TreeSet?

479
heymatthew

HashSet est beaucoup plus rapide que TreeSet (temps constant contre temps journal pour la plupart des opérations comme ajouter, supprimer et contient) mais n'offre aucune garantie d'ordre comme TreeSet.

HashSet

  • la classe offre des performances constantes dans le temps pour les opérations de base (ajout, suppression, contenu et taille).
  • il ne garantit pas que l'ordre des éléments restera constant dans le temps
  • les performances des itérations dépendent de la capacité initiale et du facteur de charge du HashSet.
    • Il est tout à fait sûr d’accepter le facteur de charge par défaut, mais vous pouvez spécifier une capacité initiale environ deux fois supérieure à celle prévue pour l’ensemble.

TreeSet

  • garanties log (n) coût de temps pour les opérations de base (ajouter, supprimer et contient)
  • garantit que les éléments de l'ensemble seront triés (croissant, naturel ou celui spécifié par vous via son constructeur) (implémente SortedSet )
  • n'offre aucun paramètre de réglage pour les performances d'itération
  • propose quelques méthodes pratiques pour traiter le jeu ordonné, telles que first() , last(), headSet() , et tailSet() etc

Les points importants:

  • Les deux garantissent une collection d'éléments sans doublon
  • Il est généralement plus rapide d'ajouter des éléments au HashSet, puis de convertir la collection en TreeSet pour une traversée triée sans doublon.
  • Aucune de ces implémentations n'est synchronisée. C'est-à-dire que si plusieurs threads accèdent simultanément à un ensemble et qu'au moins l'un des threads modifie l'ensemble, celui-ci doit être synchronisé en externe.
  • LinkedHashSet ​​est en quelque sorte intermédiaire entre HashSet et TreeSet. Mis en œuvre sous la forme d'une table de hachage avec une liste chaînée qui la traverse, cependant, fournit une itération ordonnée par insertion qui n'est pas identique à la traversée triée garantie par TreeSet.

Le choix de l’utilisation dépend donc entièrement de vos besoins, mais j’estime que même si vous avez besoin d’une collection ordonnée, vous devriez tout de même préférer HashSet pour créer le jeu puis le convertir en TreeSet.

  • par exemple. SortedSet<String> s = new TreeSet<String>(hashSet);
843
sactiw

Un avantage non encore mentionné d'un TreeSet est qu'il a une "localité" plus grande, ce qui est un raccourci pour dire (1) si deux entrées sont proches dans l'ordre, un TreeSet les place les unes à côté des autres la structure de données, et donc en mémoire; et (2) ce placement tire parti du principe de localité, qui dit que des données similaires sont souvent consultées par une application avec une fréquence similaire.

Ceci est en contraste avec un HashSet, qui répartit les entrées dans toute la mémoire, quelles que soient leurs clés.

Lorsque le coût de latence de la lecture sur un disque dur est plusieurs milliers de fois supérieur à celui de la lecture en cache ou en RAM, et lorsque les données sont réellement consultées avec la localité, le paramètre TreeSet peut être un bien meilleur choix.

39
Carl Andersen

HashSet est O(1) pour accéder aux éléments, donc c'est certainement important. Mais maintenir l'ordre des objets dans l'ensemble n'est pas possible.

TreeSet est utile si le maintien d'un ordre (en termes de valeurs et non d'ordre d'insertion) vous tient à cœur. Mais, comme vous l'avez noté, vous négociez un ordre plus lent pour accéder à un élément: O (log n) pour les opérations de base.

Depuis le javadocs for TreeSet :

Cette implémentation fournit un coût en temps de log (n) garanti pour les opérations de base (add, remove et contains).

25
duffymo

1.HashSet autorise les objets nuls.

2.TreeSet n'autorisera pas d'objet null. Si vous essayez d'ajouter une valeur null, une exception NullPointerException sera générée.

3.HashSet est beaucoup plus rapide que TreeSet.

par exemple.

 TreeSet<String> ts = new TreeSet<String>();
 ts.add(null); // throws NullPointerException

 HashSet<String> hs = new HashSet<String>();
 hs.add(null); // runs fine
21
SuReN

En me basant sur la belle réponse visuelle sur les cartes de @shevchyk, voici ce que je pense:

╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗
║   Property   ║       HashSet       ║      TreeSet      ║     LinkedHashSet   ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║              ║  no guarantee order ║ sorted according  ║                     ║
║   Order      ║ will remain constant║ to the natural    ║    insertion-order  ║
║              ║      over time      ║    ordering       ║                     ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ Add/remove   ║        O(1)         ║     O(log(n))     ║        O(1)         ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║              ║                     ║   NavigableSet    ║                     ║
║  Interfaces  ║         Set         ║       Set         ║         Set         ║
║              ║                     ║    SortedSet      ║                     ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║              ║                     ║    not allowed    ║                     ║
║  Null values ║       allowed       ║ 1st element only  ║      allowed        ║
║              ║                     ║     in Java 7     ║                     ║
╠══════════════╬═════════════════════╩═══════════════════╩═════════════════════╣
║              ║   Fail-fast behavior of an iterator cannot be guaranteed      ║
║   Fail-fast  ║ impossible to make any hard guarantees in the presence of     ║
║   behavior   ║           unsynchronized concurrent modification              ║
╠══════════════╬═══════════════════════════════════════════════════════════════╣
║      Is      ║                                                               ║
║ synchronized ║              implementation is not synchronized               ║
╚══════════════╩═══════════════════════════════════════════════════════════════╝
21
kiedysktos

La raison pour laquelle la plupart des utilisateurs utilisent HashSet est que les opérations sont (en moyenne) O(1) au lieu de O (log n). Si le jeu contient des éléments standard, vous ne serez pas "déranger avec des fonctions de hachage" comme cela a été fait pour vous. Si le jeu contient des classes personnalisées, vous devez implémenter hashCode pour utiliser HashSet (bien que Effective Java montre comment), mais si vous utilisez TreeSet, vous devez faites-le Comparable ou fournissez un Comparator. Cela peut poser problème si la classe n'a pas d'ordre particulier.

J'ai parfois utilisé TreeSet (ou en fait TreeMap) pour de très petits ensembles/cartes (<10 éléments) bien que je n'ai pas vérifié s'il y avait réellement un gain à le faire. Pour les grands ensembles, la différence peut être considérable.

Maintenant, si vous avez besoin du tri, alors TreeSet est approprié, même si même si les mises à jour sont fréquentes et que la nécessité d’un résultat de tri est peu fréquente, il est parfois plus rapide de copier le contenu dans une liste ou un tableau.

13
Kathy Van Stone

Si vous n'insérez pas suffisamment d'éléments pour provoquer des modifications fréquentes (ou des collisions, si votre HashSet ne peut pas être redimensionné), un HashSet vous offre certainement l'avantage d'un accès permanent. Mais sur des ensembles avec beaucoup de croissance ou de réduction, vous pouvez réellement obtenir de meilleures performances avec les arbres, en fonction de la mise en œuvre.

Le temps amorti peut être proche de O(1) avec un arbre fonctionnel rouge-noir, si ma mémoire est bonne. Le livre d'Okasaki aurait une meilleure explication que je ne pourrais en tirer. (Ou voir sa liste de publications )

11
JasonTrue

Les implémentations de hachage sont, bien sûr, beaucoup plus rapides - moins de frais généraux car il n'y a pas de commande. Une bonne analyse des différentes implémentations de Set dans Java est fournie à l'adresse http://Java.Sun.com/docs/books/tutorial/collections/implementations/set. html .

La discussion à cet endroit met également en évidence une approche de terrain intermédiaire intéressante à la question Tree vs Hash. Java fournit un LinkedHashSet, qui est un hachage avec une liste chaînée "orientée insertion", c'est-à-dire que le dernier élément de la liste chaînée est également le dernier inséré dans le hachage. Cela vous permet d'éviter l'irrégularité d'un hachage non ordonné sans supporter le coût supplémentaire d'un TreeSet.

7
Joseph Weissman

TreeSet est l'une des deux collections triées (l'autre étant TreeMap). Il utilise une structure arborescente rouge-noire (mais vous le saviez) et garantit que les éléments seront dans l'ordre croissant, selon l'ordre naturel. Vous pouvez éventuellement créer un TreeSet avec un constructeur qui vous permet de donner à la collection vos propres règles pour ce que devrait être l'ordre (plutôt que de s'appuyer sur l'ordre défini par la classe des éléments) en utilisant un élément Comparable ou Comparator.

et A LinkedHashSet est une version ordonnée de HashSet qui gère une liste doublement liée entre tous les éléments. Utilisez cette classe au lieu de HashSet lorsque vous vous souciez de l'ordre des itérations. Lorsque vous parcourez un HashSet, l'ordre est imprévisible, tandis qu'un LinkedHashSet vous permet de parcourir les éléments dans l'ordre dans lequel ils ont été insérés.

4
subhash laghate

Pourquoi avoir des pommes quand on peut avoir des oranges?

Sérieusement, mecs et filles - si votre collection est volumineuse, lue et écrite plusieurs fois, et que vous payez pour des cycles de traitement, le choix de la collection est pertinent UNIQUEMENT si vous en avez besoin pour une meilleure performance. Cependant, dans la plupart des cas, cela n'a pas vraiment d'importance - quelques millisecondes ici et là passent inaperçues en termes humains. Si cela importait vraiment beaucoup, pourquoi n'écrivez-vous pas du code en assembleur ou en C? [Cue une autre discussion]. Donc, le fait est que si vous êtes content d'utiliser la collection que vous avez choisie et que cela résout votre problème [même si ce n'est pas spécifiquement le type de collection le mieux adapté à la tâche], assommez-vous. Le logiciel est malléable. Optimisez votre code si nécessaire. Oncle Bob dit que l'optimisation prématurée est la racine de tous les maux. Oncle Bob le dit

3
user924272

Beaucoup de réponses ont été données, basées sur des considérations techniques, en particulier autour de la performance. Selon moi, le choix entre TreeSet et HashSet est important.

Mais je dirais plutôt que le choix devrait être déterminé par conceptuel considérations en premier.

Si, pour les objets que vous avez besoin de manipuler, un ordre naturel n’a pas de sens, n’utilisez pas TreeSet.
C'est un ensemble trié, puisqu'il implémente SortedSet. Cela signifie donc que vous devez remplacer la fonction compareTo, ce qui doit être cohérent avec ce qui retourne la fonction equals. Par exemple, si vous avez un ensemble d'objets d'une classe appelée Student, alors je ne pense pas qu'un TreeSet ait un sens, car il n'y a pas d'ordre naturel entre les étudiants. Vous pouvez les classer par leur note moyenne, d'accord, mais ce n'est pas un "ordre naturel". La fonction compareTo renverrait 0 non seulement lorsque deux objets représentent le même élève, mais également lorsque deux élèves différents ont la même note. Pour le second cas, equals renverrait false (sauf si vous décidez de rendre ce dernier vrai lorsque deux étudiants différents ont la même note, ce qui donnerait à la fonction equals un sens trompeur, pour ne pas dire mauvais sens.)
Veuillez noter que la cohérence entre equals et compareTo est facultative, mais fortement recommandée. Sinon, le contrat d'interface Set est rompu, rendant votre code trompeur pour d'autres personnes, ce qui peut également entraîner un comportement inattendu.

Ce lien pourrait être une bonne source d’information sur cette question.

3
Marek Stanley

Message Edit (réécriture complète) Lorsque l'ordre n'a pas d'importance, c'est à ce moment-là. Les deux devraient donner Log (n) - il serait utile de voir si l’un est plus rapide de cinq pour cent que l’autre. HashSet peut donner O(1) le test d'une boucle doit révéler si c'est le cas.

1
Nicholas Jordan