Un guide sur TreeMap en Java

Un guide de TreeMap en Java

1. Vue d'ensemble

Dans cet article, nous allons explorer l'implémentation deTreeMap de l'interfaceMap de Java Collections Framework (JCF).

TreeMap est une implémentation de carte qui conserve ses entrées triées selon l'ordre naturel de ses clés ou mieux encore en utilisant un comparateur s'il est fourni par l'utilisateur au moment de la construction.

Auparavant, nous avons couvert les implémentations deHashMap etLinkedHashMap et nous nous rendrons compte qu'il y a pas mal d'informations sur le fonctionnement de ces classes qui sont similaires.

Il est vivement recommandé de lire les articles mentionnés avant de poursuivre avec celui-ci.

2. Tri par défaut enTreeMap

Par défaut,TreeMap trie toutes ses entrées selon leur ordre naturel. Pour un entier, cela signifierait l'ordre croissant et pour les chaînes, l'ordre alphabétique.

Voyons l'ordre naturel dans un test:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect() {
    TreeMap map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

    assertEquals("[1, 2, 3, 4, 5]", map.keySet().toString());
}

Notez que nous avons placé les clés entières de manière non ordonnée, mais lors de la récupération de l'ensemble de clés, nous confirmons qu'elles sont effectivement conservées dans l'ordre croissant. C'est l'ordre naturel des nombres entiers.

De même, lorsque nous utilisons des chaînes, elles seront triées dans leur ordre naturel, c'est-à-dire alphabétiquement:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect2() {
    TreeMap map = new TreeMap<>();
    map.put("c", "val");
    map.put("b", "val");
    map.put("a", "val");
    map.put("e", "val");
    map.put("d", "val");

    assertEquals("[a, b, c, d, e]", map.keySet().toString());
}

TreeMap, contrairement à une mappe de hachage et à une mappe de hachage liée, n'emploie nulle part le principe de hachage car il n'utilise pas de tableau pour stocker ses entrées.

3. Tri personnalisé enTreeMap

Si nous ne sommes pas satisfaits de l’ordre naturel deTreeMap, nous pouvons également définir notre propre règle de classement au moyen d’un comparateur lors de la construction d’une arborescence.

Dans l'exemple ci-dessous, nous souhaitons que les clés entières soient ordonnées par ordre décroissant:

@Test
public void givenTreeMap_whenOrdersEntriesByComparator_thenCorrect() {
    TreeMap map =
      new TreeMap<>(Comparator.reverseOrder());
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

    assertEquals("[5, 4, 3, 2, 1]", map.keySet().toString());
}

Une carte de hachage ne garantit pas l'ordre des clés stockées et ne garantit pas en particulier que cet ordre restera le même dans le temps, mais une carte en arbre garantit que les clés seront toujours triées selon l'ordre spécifié.

4. Importance du triTreeMap

Nous savons maintenant queTreeMap stocke toutes ses entrées dans un ordre trié. En raison de cet attribut d'arborescence, nous pouvons effectuer des requêtes telles que; trouver «le plus grand», trouver le «plus petit», trouver toutes les clés inférieures ou supérieures à une certaine valeur, etc.

Le code ci-dessous ne couvre qu'un petit pourcentage de ces cas:

@Test
public void givenTreeMap_whenPerformsQueries_thenCorrect() {
    TreeMap map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

    Integer highestKey = map.lastKey();
    Integer lowestKey = map.firstKey();
    Set keysLessThan3 = map.headMap(3).keySet();
    Set keysGreaterThanEqTo3 = map.tailMap(3).keySet();

    assertEquals(new Integer(5), highestKey);
    assertEquals(new Integer(1), lowestKey);
    assertEquals("[1, 2]", keysLessThan3.toString());
    assertEquals("[3, 4, 5]", keysGreaterThanEqTo3.toString());
}

5. Implémentation interne deTreeMap

TreeMap implémente l'interface deNavigableMap et base son fonctionnement interne sur les principes des arbres rouge-noir:

public class TreeMap extends AbstractMap
  implements NavigableMap, Cloneable, java.io.Serializable

Le principe des arbres rouge-noir dépasse le cadre de cet article, cependant, il y a des points clés à retenir pour comprendre comment ils s'intègrent dansTreeMap.

First of all, un arbre rouge-noir est une structure de données composée de nœuds; imaginez un manguier inversé avec sa racine dans le ciel et les branches poussant vers le bas. La racine contiendra le premier élément ajouté à l'arbre.

La règle est que, à partir de la racine, tout élément de la branche gauche d’un nœud est toujours inférieur à l’élément du nœud lui-même. Ceux de droite sont toujours plus grands. Ce qui définit plus ou moins que est déterminé par l'ordre naturel des éléments ou par le comparateur défini lors de la construction, comme nous l'avons vu précédemment.

Cette règle garantit que les entrées d'un treemap seront toujours triées et prévisibles.

Secondly, un arbre rouge-noir est un arbre de recherche binaire auto-équilibré. Cet attribut et les éléments ci-dessus garantissent que les opérations de base comme rechercher, obtenir, placer et supprimer prennent un temps logarithmiqueO(log n).

Être auto-équilibré est la clé ici. Tandis que nous continuons à insérer et à supprimer des entrées, imaginez que l'arbre pousse de plus en plus long d'un côté ou de l'autre côté.

Cela signifierait qu'une opération prendrait moins de temps sur la branche la plus courte et plus de temps sur la branche la plus éloignée de la racine, ce que nous ne voudrions pas.

Par conséquent, cela est pris en compte dans la conception des arbres rouge-noir. Pour chaque insertion et suppression, la hauteur maximale de l'arbre sur n'importe quelle arête est maintenue àO(log n) i.e. l'arbre se balance continuellement.

Tout comme les cartes de hachage et les cartes de hachage liées, une carte en arborescence n'est pas synchronisée et les règles d'utilisation de cette dernière dans un environnement multithread sont similaires à celles des deux autres implémentations de carte.

6. Choisir la bonne carte

Après avoir regardé les implémentations deHashMap etLinkedHashMap auparavant et maintenantTreeMap, il est important de faire une brève comparaison entre les trois pour nous guider sur celle qui se situe où.

A hash map est une bonne implémentation de carte à usage général qui fournit des opérations de stockage et de récupération rapides. Cependant, il est insuffisant en raison de son agencement chaotique et non ordonné des entrées.

Cela se traduit par des performances médiocres dans les scénarios où il y a beaucoup d'itérations car la capacité totale de la matrice sous-jacente affecte la traversée autre que le nombre d'entrées.

A linked hash map possède les bons attributs des cartes de hachage et ajoute de l'ordre aux entrées. Il fonctionne mieux lorsqu'il y a beaucoup d'itérations car seul le nombre d'entrées est pris en compte, quelle que soit la capacité.

A tree map prend la commande au niveau suivant en fournissant un contrôle complet sur la façon dont les clés doivent être triées. D'un autre côté, il offre une performance générale inférieure aux deux autres alternatives.

On pourrait dire unlinked hash map reduces the chaos in the ordering of a hash map without incurring the performance penalty of a tree map.

7. Conclusion

Dans cet article, nous avons exploré la classe JavaTreeMap et son implémentation interne. Comme il s’agit de la dernière d’une série d’implémentations communes d’interfaces Map, nous avons également discuté brièvement des domaines dans lesquels elle correspond le mieux aux deux autres.

Le code source complet de tous les exemples utilisés dans cet article se trouve dans lesGitHub project.