HashMap vs Hashtable en Java : qui l'emporte ? Regardons sous le capot
\n\nSi vous vous êtes déjà demandé : « Quelle est la différence entre HashMap et Hashtable ? » — vous n'êtes pas seul. C'est l'une des questions les plus populaires sur Stack Overflow, et pour cause. Les deux classes stockent des données au format « clé — valeur », toutes deux utilisent le hachage, mais elles vivent dans des mondes différents. L'une est rapide et moderne, l'autre est ancienne, fiable, mais un peu lourde. Regardons-les « sous le capot » et comprenons ce qu'il en est.
\n\nDans cet article, nous ne nous contenterons pas de lister les différences. Nous comprendrons comment fonctionne la mécanique interne, pourquoi une classe autorise null et l'autre non, et quand utiliser ConcurrentHashMap à la place des deux.
Bref retour historique
\n\nHashtable est apparu en Java dès la version 1.0. C'était l'une des premières classes de collections, et elle était synchronisée dès le départ. À l'époque, le multithreading était rare, mais les développeurs de Java ont préféré jouer la sécurité. Plus tard, dans Java 2 (1.2), de nouvelles collections du framework Collections Framework ont vu le jour, dont HashMap. Elle n'était pas synchronisée, mais beaucoup plus rapide. Depuis, Hashtable est considérée comme obsolète (legacy), bien qu'elle n'ait jamais été supprimée du langage.
\n\nDifférence principale : synchronisation et threads
\n\nLa première et la plus importante différence est la synchronisation. Les méthodes de Hashtable (par exemple, put(), get(), remove()) sont marquées avec le mot-clé synchronized. Cela signifie que deux threads ne peuvent pas exécuter simultanément la même méthode sur un même objet Hashtable. Cela semble être un avantage, mais en pratique, cela devient un goulot d'étranglement.
// Hashtable — toutes les méthodes sont synchronisées\nHashtable<String, String> table = new Hashtable<>();\ntable.put("key1", "value1"); // thread-safe, mais lent en cas de forte concurrence\n\n// HashMap — non synchronisé\nHashMap<String, String> map = new HashMap<>();\nmap.put("key1", "value1"); // rapide, mais non thread-safe\n\n\nSi vous travaillez dans une application monothread (ce qui est le cas le plus courant), HashMap sera plus rapide car il n'a pas besoin de perdre du temps à acquérir et libérer des verrous. Si vous avez besoin de sécurité des threads, il est préférable de ne pas utiliser Hashtable, mais d'opter pour ConcurrentHashMap (apparu dans Java 5). Il utilise un verrouillage plus fin (segments ou verrous au niveau des buckets) et fonctionne beaucoup plus rapidement dans un environnement multithread.
Null, null et encore null
\n\nLa deuxième différence importante est la gestion de null. HashMap permet d'utiliser null à la fois comme clé (une seule fois) et comme valeur (autant de fois que souhaité). Hashtable, quant à elle, lèvera une NullPointerException si vous essayez de passer null.
HashMap<String, String> map = new HashMap<>();\nmap.put(null, "nullKey"); // OK\nmap.put("key", null); // OK\nSystem.out.println(map.get(null)); // "nullKey"\n\nHashtable<String, String> table = new Hashtable<>();\n// table.put(null, "value"); // NullPointerException !\n// table.put("key", null); // NullPointerException !\n\n\nPourquoi cela ? Tout est lié au fonctionnement du hachage. Dans HashMap, pour la clé null, un code de hachage spécial 0 est utilisé, et elle est placée dans le bucket zéro. Hashtable utilise un mécanisme plus ancien où l'appel de hashCode() sur null provoque une erreur. De plus, Hashtable ne gère pas les valeurs null, car cela violerait la logique des opérations synchronisées.
Fonctionnement interne : comment fonctionne une table de hachage sous le capot
\n\nPour comprendre la différence plus en profondeur, regardons à l'intérieur. HashMap et Hashtable sont toutes deux basées sur une table de hachage. Il s'agit d'un tableau de « buckets », où chaque bucket est une liste chaînée (ou un arbre, à partir de Java 8). Lorsque vous ajoutez