Java — HashMap
Principe
HashMap<K, V> est la structure clé-valeur la plus utilisée en Java. Elle permet un accès en O(1) en moyenne grâce au hashing.
Fonctionnement interne
Structure
HashMap = tableau de buckets (Node[])
Chaque bucket contient une liste chaînée (ou un arbre rouge-noir si > 8 éléments).
Processus de put
- Calculer
hashCode()de la clé - Déterminer l'index du bucket :
index = hash & (capacity - 1) - Si le bucket est vide → insérer le nœud
- Si le bucket est occupé → parcourir la liste chaînée
- si une clé
equals()existe → remplacer la valeur - sinon → ajouter en fin de liste
- si une clé
Processus de get
- Calculer le hash de la clé
- Aller au bucket correspondant
- Parcourir la liste/arbre et comparer avec
equals()
Exemple
Map<String, Integer> scores = new HashMap<>();
scores.put("Ali", 95);
scores.put("Sara", 88);
int score = scores.get("Ali"); // 95
scores.getOrDefault("Unknown", 0); // 0
Capacity et Load Factor
new HashMap<>(16, 0.75f);
// ^capacity ^loadFactor
| Paramètre | Défaut | Rôle |
|---|---|---|
| Initial capacity | 16 | Taille initiale du tableau |
| Load factor | 0.75 | Seuil de remplissage avant resize |
Quand size > capacity * loadFactor, la HashMap double sa capacité et rehash toutes les entrées.
Warning
Le rehashing est coûteux en O(n). Si le nombre d'éléments est connu à l'avance, initialiser avec la bonne capacité.
hashCode() et equals()
Le contrat est strict :
- si
a.equals(b)→a.hashCode() == b.hashCode()(obligatoire) - si
a.hashCode() == b.hashCode()→a.equals(b)n'est pas garanti (collision)
Un mauvais hashCode() concentre les éléments dans peu de buckets → O(n) au lieu de O(1).
// Mauvais — toutes les clés dans le même bucket
@Override
public int hashCode() { return 1; }
// Bon — distribution uniforme
@Override
public int hashCode() { return Objects.hash(name, email); }
Null keys et values
map.put(null, "value"); // OK — une seule clé null autorisée
map.put("key", null); // OK — valeurs null autorisées
À noter : Hashtable et ConcurrentHashMap n'acceptent pas les null.
Itération
// Par entrées
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
// Par clés
for (String key : map.keySet()) { ... }
// Par valeurs
for (Integer value : map.values()) { ... }
Warning
L'ordre d'itération n'est pas garanti. Pour un ordre prévisible, utiliser LinkedHashMap (insertion) ou TreeMap (tri naturel).
Complexité
| Opération | Moyenne | Pire cas |
|---|---|---|
put |
O(1) | O(n) |
get |
O(1) | O(n) |
remove |
O(1) | O(n) |
containsKey |
O(1) | O(n) |
containsValue |
O(n) | O(n) |
Le pire cas (O(n)) survient quand toutes les clés tombent dans le même bucket. Depuis Java 8, les buckets avec plus de 8 éléments sont convertis en arbres rouge-noir → O(log n).
Thread safety
HashMap n'est pas thread-safe. En environnement concurrent :
- utiliser
ConcurrentHashMap - ou
Collections.synchronizedMap(new HashMap<>())(moins performant)
À retenir
- HashMap offre O(1) en moyenne pour get/put
- toujours redéfinir
hashCode()etequals()ensemble pour les clés custom - pas de garantie d'ordre — utiliser LinkedHashMap ou TreeMap si nécessaire
- pas thread-safe — utiliser ConcurrentHashMap en concurrent
- initialiser avec la bonne capacité si le nombre d'éléments est connu