Au-delà du problème de la boucle infinie, HashMap peut présenter d'autres inconvénients. Ces problèmes découlent généralement d'une mauvaise utilisation de la structure de données et peuvent être classés en deux catégories principales :
- Problèmes liés à l'implémentation : Par exemple, dans les versions JDK 1.7, l'insertion concurrente pouvait entraîner des boucles infinies ou des écrasements de données.
- Problèmes liés aux fonctionnalités : L'absence d'ordre garanti dans
HashMappeut conduire à des résultats de recherche incohérents par rapport aux attentes.
- Problème de la boucle infinie
Ce problème, observé dans JDK 1.7, est causé par la méthode d'insertion en tête lors de l'opération d'extension (redimensionnement) concurrente. Examinons le déroulement :
1.1 Scénario de boucle infinie - Étape 1
La boucle infinie suvrient lors de l'extension concurrente. Deux threads, T1 et T2, tentent d'étendre le HashMap. Initialement, les deux threads pointent vers le premier élément de la liste chaînée, A. Leurs pointeurs suivants, T1.next et T2.next, pointent tous deux vers le nœud B.
1.2 Scénario de boucle infinie - Étape 2
Le thread T2 voit sa tranche de temps expirer et entre en veille. Pendant ce temps, le thread T1 exécute l'opération d'extension. Une fois terminé, le thread T1 est réveillé. L'état après l'extension par T1 est le suivant :
En raison de l'insertion en tête, l'ordre des éléments dans le HashMap a changé. Le thread T2, ignorant ces modifications, conserve ses pointeurs d'origine, pointant toujours vers A, avec T2.next pointant vers B.
1.3 Scénario de boucle infinie - Étape 3
Lorsque le thread T2 reprend son exécution, la boucle infinie est établie. L'ordre des nœuds après l'extension par T1 est désormais B puis A. Le thread T2, toujours positionné sur A avec B comme suivant, se retrouve dans une situation où A pointe vers B et B pointe vers A. Cela crée une boucle infinie.
1.4 Solution
Pour résoudre ce problème, remplacez HashMap par une structure de données thread-safe comme ConcurrentHashMap ou Hashtable. ConcurrentHashMap offre généralement de meilleures performances et est donc le choix recommandé.
- Problème d'écrasement de données
Ce problème survient lors de l'ajout concurrent d'éléments et n'est pas limité aux versions JDK 1.7.
- Le thread T1 détermine un emplacement approprié pour insérer un nouvel élément mais est interrompu avant de finaliser l'insertion.
- Le thread T2, générant un code de hachage identique, tente d'insérer un élément au même emplacement. Puisque T1 n'a pas encore inséré sa valeur, T2 procède à l'insertion.
- Lorsque T1 reprend son exécution, il ignore la présence de la valeur insérée par T2 et insère sa propre valeur au même emplacement, écrasant ainsi la valeur de T2.
2.1 Scénario d'écrasement - Étape 1
Le thread T1 se prépare à insérer la paire clé-valeur k1:v1 à un emplacement actuellement vide (Null). Cependant, son exécution est interrompue avant l'insertion.
2.2 Scénario d'écrasement - Étape 2
Le thread T2 tente d'insérer la paire k2:v2. Il vérifie l'emplacement et, le trouvant vide, procède à l'insertion.
2.3 Scénario d'écrasement - Étape 3
Une fois T2 terminé, T1 reprend son exécution. N'ayant pas détecté la valeur insérée par T2, il insère sa propre valeur, écrasant ainsi celle de T2.
2.4 Solution
La solution est la même que pour le problème de la boucle infinie : utilisez ConcurrentHashMap pour remplacer HashMap.
- Problème d'absence d'ordre
Ce problème se réfère à la divergence entre l'ordre d'insertion des éléments et l'ordre de leur récupération dans un HashMap, ce qui peut entraîner des résultats inattendus.
Considérez l'exemple de code suivant :
HashMap<String, String> map = new HashMap<>();
// Ajout d'éléments
for (int i = 1; i <= 5; i++) {
map.put("2022-10-" + i, "Hello, Java: " + i);
}
// Récupération des éléments
map.forEach((k, v) -> {
System.out.println(k + ": " + v);
});
L'ordre d'insertion est séquentiel. Cependant, l'exécution de ce code ne produit pas l'ordre d'insertion attendu. La sortie réelle peut varier, par exemple :
2022-10-2: Hello, Java: 2
2022-10-1: Hello, Java: 1
2022-10-4: Hello, Java: 4
2022-10-3: Hello, Java: 3
2022-10-5: Hello, Java: 5
L'ordre de sortie ne correspond pas à l'ordre d'insertion, démontrant le problème d'absence d'ordre de HashMap.
Solution
Pour garantir que l'ordre d'insertion et de récupération soit le même, remplacez HashMap par LinkedHashMap.
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// Ajout d'éléments
for (int i = 1; i <= 5; i++) {
map.put("2022-10-" + i, "Hello, Java: " + i);
}
// Récupération des éléments
map.forEach((k, v) -> {
System.out.println(k + ": " + v);
});
L'exécution de ce code avec LinkedHashMap produira la sortie dans l'ordre d'insertion attendu.
En résumé, les problèmes de boucle infinie et d'écrasement de données surviennent lors d'additions concurrentes, tandis que le problème d'absence d'ordre concerne la divergence entre l'ordre d'insertion et de récupération. Ces problèmes découlent souvent d'une mauvaise utilisation. L'utilisation de ConcurrentHashMap est conseillée pour les scénarios multithread, et LinkedHashMap garantit la préservation de l'ordre d'insertion.