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
?
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.
SortedSet
)first()
, last()
, headSet()
, et tailSet()
etcHashSet
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.
SortedSet<String> s = new TreeSet<String>(hashSet);
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.
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
etcontains
).
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
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 ║
╚══════════════╩═══════════════════════════════════════════════════════════════╝
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.
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 )
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.
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.
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
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.
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.