Java Language
TreeMap et TreeSet
Recherche…
Introduction
TreeMap
et TreeSet
sont des collections Java de base ajoutées à Java 1.2. TreeMap
est une implémentation de Map
mutable , ordonnée . De même, TreeSet
est une implémentation d' Set
ordonnée et mutable .
TreeMap
est implémenté comme un arbre rouge-noir, qui fournit des temps d'accès O(log n)
. TreeSet
est implémenté en utilisant un TreeMap
avec des valeurs factices.
Les deux collections ne sont pas compatibles avec les threads.
TreeMap d'un type Java simple
Tout d'abord, nous créons une carte vide et y insérons des éléments:
TreeMap<Integer, String> treeMap = new TreeMap<>();
TreeMap<Integer, String> treeMap = new TreeMap<Integer, String>();
treeMap.put(10, "ten");
treeMap.put(4, "four");
treeMap.put(1, "one");
treeSet.put(12, "twelve");
Une fois que nous avons quelques éléments dans la carte, nous pouvons effectuer certaines opérations:
System.out.println(treeMap.firstEntry()); // Prints 1=one
System.out.println(treeMap.lastEntry()); // Prints 12=twelve
System.out.println(treeMap.size()); // Prints 4, since there are 4 elemens in the map
System.out.println(treeMap.get(12)); // Prints twelve
System.out.println(treeMap.get(15)); // Prints null, since the key is not found in the map
Nous pouvons également parcourir les éléments de la carte en utilisant soit un itérateur, soit une boucle foreach. Notez que les entrées sont imprimées en fonction de leur ordre naturel et non de l'ordre d'insertion:
for (Entry<Integer, String> entry : treeMap.entrySet()) {
System.out.print(entry + " "); //prints 1=one 4=four 10=ten 12=twelve
}
Iterator<Entry<Integer, String>> iter = treeMap.entrySet().iterator();
while (iter.hasNext()) {
System.out.print(iter.next() + " "); //prints 1=one 4=four 10=ten 12=twelve
}
TreeSet d'un type Java simple
Tout d'abord, nous créons un ensemble vide et y insérons des éléments:
TreeSet<Integer> treeSet = new TreeSet<>();
TreeSet<Integer> treeSet = new TreeSet<Integer>();
treeSet.add(10);
treeSet.add(4);
treeSet.add(1);
treeSet.add(12);
Une fois que nous avons quelques éléments dans l'ensemble, nous pouvons effectuer certaines opérations:
System.out.println(treeSet.first()); // Prints 1
System.out.println(treeSet.last()); // Prints 12
System.out.println(treeSet.size()); // Prints 4, since there are 4 elemens in the set
System.out.println(treeSet.contains(12)); // Prints true
System.out.println(treeSet.contains(15)); // Prints false
Nous pouvons également parcourir les éléments de la carte en utilisant soit un itérateur, soit une boucle foreach. Notez que les entrées sont imprimées en fonction de leur ordre naturel et non de l'ordre d'insertion:
for (Integer i : treeSet) {
System.out.print(i + " "); //prints 1 4 10 12
}
Iterator<Integer> iter = treeSet.iterator();
while (iter.hasNext()) {
System.out.print(iter.next() + " "); //prints 1 4 10 12
}
TreeMap / TreeSet d'un type Java personnalisé
Depuis que TreeMap
s et TreeSet
conservent les clés / éléments en fonction de leur ordre naturel . Les clés TreeMap
et les éléments TreeSet
doivent donc être comparables.
Disons que nous avons une classe Person
:
public class Person {
private int id;
private String firstName, lastName;
private Date birthday;
//... Constuctors, getters, setters and various methods
}
Si nous le stockons tel quel dans un TreeSet
(ou une clé dans un TreeMap):
TreeSet<Person2> set = ...
set.add(new Person(1,"first","last",Date.from(Instant.now())));
Ensuite, nous avons rencontré une exception telle que celle-ci:
Exception in thread "main" java.lang.ClassCastException: Person cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1294)
at java.util.TreeMap.put(TreeMap.java:538)
at java.util.TreeSet.add(TreeSet.java:255)
Pour corriger cela, supposons que nous voulions ordonner les instances de Person
fonction de l'ordre de leurs identifiants ( private int id
). Nous pourrions le faire de deux manières:
Une solution consiste à modifier la
Person
afin qu'elle implémente l' interface Comparable :public class Person implements Comparable<Person> { private int id; private String firstName, lastName; private Date birthday; //... Constuctors, getters, setters and various methods @Override public int compareTo(Person o) { return Integer.compare(this.id, o.id); //Compare by id } }
Une autre solution consiste à fournir à
TreeSet
un comparateur :
TreeSet<Person> treeSet = new TreeSet<>((personA, personB) -> Integer.compare(personA.getId(), personB.getId()));
TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>(){
@Override
public int compare(Person personA, Person personB) {
return Integer.compare(personA.getId(), personB.getId());
}
});
Cependant, il y a deux mises en garde pour les deux approches:
Il est très important de ne modifier aucun champ utilisé pour la commande une fois qu'une instance a été insérée dans un
TreeSet
/TreeMap
. Dans l'exemple ci-dessus, si nous modifions l'id
d'une personne déjà insérée dans la collection, nous pourrions rencontrer un comportement inattendu.Il est important de mettre en œuvre la comparaison correctement et de manière cohérente. Selon le Javadoc :
Le réalisateur doit assurer
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
pour tous les x et y. (Cela implique quex.compareTo(y)
doit lancer une exception ssiy.compareTo(x)
lance une exception)L’implémenteur doit également s’assurer que la relation est transitive:
(x.compareTo(y)>0 && y.compareTo(z)>0)
impliquex.compareTo(z)>0
.Enfin, l'implémenteur doit s'assurer que
x.compareTo(y)==0
implique quesgn(x.compareTo(z)) == sgn(y.compareTo(z))
, pour tout z.
TreeMap et TreeSet Thread Safety
TreeMap
et TreeSet
ne sont pas des collections thread-safe, il convient donc de veiller à ce qu'ils soient utilisés dans des programmes multithread.
TreeMap
et TreeSet
sont tous deux sûrs lorsqu'ils sont lus, même simultanément, par plusieurs threads. Donc, si elles ont été créées et remplies par un seul thread (par exemple, au début du programme), et seulement après avoir lu, mais pas modifié par plusieurs threads, il n'y a aucune raison de synchroniser ou de verrouiller.
Toutefois, si elle est lue et modifiée simultanément ou modifiée simultanément par plusieurs threads, la collection peut générer une exception ConcurrentModificationException ou se comporter de manière inattendue. Dans ces cas, il est impératif de synchroniser / verrouiller l’accès à la collection en utilisant l’une des méthodes suivantes:
Utilisation de
Collections.synchronizedSorted..
:SortedSet<Integer> set = Collections.synchronizedSortedSet(new TreeSet<Integer>()); SortedMap<Integer,String> map = Collections.synchronizedSortedMap(new TreeMap<Integer,String>());
Cela fournira une SortedSet / SortedMap mise en œuvre soutenue par la collecte effective et synchronisée sur un objet mutex. Notez que cela synchronisera tous les accès en lecture et en écriture à la collection sur un seul verrou, de sorte que même les lectures simultanées ne seraient pas possibles.
En synchronisant manuellement certains objets, comme la collection elle-même:
TreeSet<Integer> set = new TreeSet<>();
...
//Thread 1 synchronized (set) { set.add(4); }
...
//Thread 2 synchronized (set) { set.remove(5); }
En utilisant un verrou, tel qu'un ReentrantReadWriteLock :
TreeSet<Integer> set = new TreeSet<>(); ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
...
//Thread 1 lock.writeLock().lock(); set.add(4); lock.writeLock().unlock();
...
//Thread 2 lock.readLock().lock(); set.contains(5); lock.readLock().unlock();
Contrairement aux méthodes de synchronisation précédentes, l'utilisation d'un ReadWriteLock permet à plusieurs threads de lire simultanément sur la carte.