HashMap en Java - java.util.HashMap

13 Sep 2019 13 Sep 2019 15226 vues ESSADDOUKI Mostafa 15 min de lecture
Introduction
1 Nouveautés de Java 11 2 Différences entre JDK, JRE et JVM 3 Structure d'un programme Java - Hello World 4 Mots clés et conventions de dénomination en Java 5 Types de données intégrés en Java 6 Les variables en Java 7 Classes enveloppe - Number, Integer, Double ... 8 Lire les entrées clavier en Java
Structures de contrôle
9 Les opérateurs en Java 10 Les structures conditionnelles en Java 11 Les boucles en Java 12 Instructions de contrôle de boucle - break, continue
Chaines de caractères
13 Les chaines en Java - API String 14 Les chaines en Java - StringBuffer et StringBuilder 15 Les expressions régulières en Java
Programmation OO
16 Objets et classes en Java 17 Modificateurs d'accès Java - public, private, protected et package 18 Méthodes et surcharge des méthodes en Java 19 les constructeurs en Java 20 L'héritage en Java 21 Classes abstraites en Java 22 Interfaces et héritage multiple en Java 23 Les classes imbriquées en Java 24 Les singletons en Java 25 Classes et méthodes génériques 26 Interface fonctionnelle et expressions Lambda en Java
Tableaux et collections
27 Les tableaux en Java 28 Classe Arrays - java.util.Arrays 29 Les listes dynamiques - java.util.ArrayList 30 Les listes chaînées en Java - java.util.LinkedList 31 HashSet en Java - java.util.HashSet 32 HashMap en Java - java.util.HashMap
Gestion des fichiers
33 Comprendre les fichiers informatiques 34 Utilisation des classes Path et Files en Java 35 Lecture et écriture dans un fichier en Java 36 Fichiers à accès aléatoire en Java
Gestion d'exceptions
37 Gestion d'exceptions en Java 38 Créez vos propres classes d'exception en Java
Programmation concurrente
39 Introduction à la programmation concurrente en Java - Multi-threads 40 classe java.lang.Thread 41 Synchronisation des threads en Java
Cours Java pour les débutants — Étape 32 sur 41

HashMap — Tables de hachage en Java

  Prérequis

Maîtriser les collections Java et les génériques. Comprendre le concept de clé/valeur. Connaître les méthodes hashCode() et equals().

  Objectifs

Comprendre le principe des tables de hachage et leur implémentation avec HashMap. Maîtriser les opérations d'insertion, de recherche et de suppression. Connaître les bonnes pratiques (clés immutables, redéfinition de hashCode()/equals()).

1. Qu'est-ce qu'un HashMap ?

HashMap

HashMap est une structure de données qui stocke des paires clé/valeur (associations). Chaque clé est unique et permet de retrouver rapidement la valeur associée. La recherche se fait par la clé, pas par une copie exacte de l'objet (contrairement à Set).

Exemple d'utilisation : stocker des employés avec leur identifiant comme clé.

   
Syntaxe — Création d'un HashMap Java
HashMap<K, V> map = new HashMap<>();

// Avec var (Java 10+)
var map = new HashMap<K, V>();

  Exemple 1 — Stockage d'employés

import java.util.HashMap;

class Employe {
    private Integer id;
    private String nom;
    private String prenom;

    public Employe(Integer id, String nom, String prenom) {
        this.id = id;
        this.nom = nom;
        this.prenom = prenom;
    }

    @Override
    public String toString() {
        return "Employe [id=" + id + ", nom=" + nom + ", prenom=" + prenom + "]";
    }
}

public class Test {
    public static void main(String[] args) {
        var employes = new HashMap<Integer, Employe>();

        employes.put(1, new Employe(1, "ESSADDOUKI", "Mostafa"));
        employes.put(4, new Employe(4, "KAYOUH", "Mohamed"));
        employes.put(7, new Employe(7, "Moutawakil", "Dounia"));

        System.out.println("HashMap : " + employes);
    }
}
Sortie
HashMap : {1=Employe [id=1, nom=ESSADDOUKI, prenom=Mostafa], 4=Employe [id=4, nom=KAYOUH, prenom=Mohamed], 7=Employe [id=7, nom=Moutawakil, prenom=Dounia]}

2. Constructeurs de HashMap

Constructeur Description
HashMap<K, V>() Capacité initiale 16, facteur de charge 0.75
HashMap<K, V>(int capacity) Capacité initiale spécifiée, facteur de charge 0.75
HashMap<K, V>(int capacity, float loadFactor) Capacité et facteur de charge spécifiés
HashMap<K, V>(Map<? extends K, ? extends V> m) Crée un HashMap à partir d'une autre Map
Facteur de charge (load factor)

Le facteur de charge (0.75 par défaut) contrôle le moment où la table de hachage est agrandie. Un facteur plus élevé économise de la mémoire mais réduit les performances. Un facteur plus bas améliore les performances mais consomme plus de mémoire.

3. Méthodes principales

Méthode Description Complexité
V put(K key, V value) Insère une paire clé/valeur (remplace si clé existante) O(1) amorti
V get(Object key) Retourne la valeur associée à la clé (ou null) O(1) amorti
V remove(Object key) Supprime la paire associée à la clé O(1) amorti
boolean containsKey(Object key) Vérifie si la clé existe O(1) amorti
boolean containsValue(Object value) Vérifie si la valeur existe O(n)
int size() Nombre de paires clé/valeur O(1)
boolean isEmpty() Vérifie si la map est vide O(1)
void clear() Supprime toutes les entrées O(n)
Set<K> keySet() Retourne un ensemble des clés O(1)
Collection<V> values() Retourne une collection des valeurs O(1)
Set<Map.Entry<K,V>> entrySet() Retourne un ensemble des entrées O(1)

4. Opérations de base

  Exemple 2 — Manipulation d'un HashMap

import java.util.HashMap;

class Employe {
    private Integer id;
    private String nom;
    private String prenom;

    public Employe(Integer id, String nom, String prenom) {
        this.id = id;
        this.nom = nom;
        this.prenom = prenom;
    }

    @Override
    public String toString() {
        return "Employe [id=" + id + ", nom=" + nom + ", prenom=" + prenom + "]";
    }
}

public class Test {
    public static void main(String[] args) {
        var employes = new HashMap<Integer, Employe>();

        // Ajout d'éléments
        employes.put(1, new Employe(1, "ESSADDOUKI", "Mostafa"));
        employes.put(4, new Employe(4, "KAYOUH", "Mohamed"));
        employes.put(7, new Employe(7, "Moutawakil", "Dounia"));

        System.out.println("Taille : " + employes.size());

        // Vérification et récupération
        if (employes.containsKey(4)) {
            Employe emp = employes.get(4);
            System.out.println("Employé avec clé 4 : " + emp);
        }

        // Affichage de toutes les valeurs
        System.out.println("Tous les employés : " + employes.values());

        // Suppression de toutes les entrées
        employes.clear();
        if (employes.isEmpty()) {
            System.out.println("HashMap est vide");
        }
    }
}
Sortie
Taille : 3
Employé avec clé 4 : Employe [id=4, nom=KAYOUH, prenom=Mohamed]
Tous les employés : [Employe [id=1, nom=ESSADDOUKI, prenom=Mostafa], Employe [id=4, nom=KAYOUH, prenom=Mohamed], Employe [id=7, nom=Moutawakil, prenom=Dounia]]
HashMap est vide

5. Parcours d'un HashMap

  Exemple 3 — Différentes façons de parcourir

import java.util.HashMap;
import java.util.Map;

public class ParcoursHashMap {
    public static void main(String[] args) {
        HashMap<String, Integer> notes = new HashMap<>();
        notes.put("Mostafa", 18);
        notes.put("Amal", 16);
        notes.put("Youssef", 14);
        notes.put("Fatima", 17);

        // 1. Parcours des clés
        System.out.println("=== Parcours des clés ===");
        for (String nom : notes.keySet()) {
            System.out.println("Clé : " + nom);
        }

        // 2. Parcours des valeurs
        System.out.println("\n=== Parcours des valeurs ===");
        for (Integer note : notes.values()) {
            System.out.println("Valeur : " + note);
        }

        // 3. Parcours des entrées (clé + valeur) — recommandé
        System.out.println("\n=== Parcours des entrées ===");
        for (Map.Entry<String, Integer> entry : notes.entrySet()) {
            System.out.println(entry.getKey() + " → " + entry.getValue());
        }

        // 4. Avec forEach() + lambda (Java 8+)
        System.out.println("\n=== forEach() avec lambda ===");
        notes.forEach((nom, note) -> System.out.println(nom + " → " + note));
    }
}
Sortie
=== Parcours des clés ===
Clé : Mostafa
Clé : Amal
Clé : Youssef
Clé : Fatima

=== Parcours des valeurs ===
Valeur : 18
Valeur : 16
Valeur : 14
Valeur : 17

=== Parcours des entrées ===
Mostafa → 18
Amal → 16
Youssef → 14
Fatima → 17

=== forEach() avec lambda ===
Mostafa → 18
Amal → 16
Youssef → 14
Fatima → 17

6. Méthodes spécifiques (Java 8+)

  Exemple 4 — Méthodes modernes

import java.util.HashMap;

public class MethodesModernes {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();

        // putIfAbsent — ajoute seulement si la clé n'existe pas
        map.putIfAbsent("A", 10);
        map.putIfAbsent("A", 20);  // Ignoré
        System.out.println("putIfAbsent : " + map);

        // computeIfAbsent — calcule la valeur si la clé est absente
        map.computeIfAbsent("B", k -> k.length() * 5);
        System.out.println("computeIfAbsent : " + map);

        // computeIfPresent — modifie la valeur si la clé existe
        map.computeIfPresent("A", (k, v) -> v + 100);
        System.out.println("computeIfPresent : " + map);

        // getOrDefault — retourne une valeur par défaut si clé absente
        Integer valeur = map.getOrDefault("Z", 0);
        System.out.println("getOrDefault('Z') : " + valeur);

        // merge — fusionne les valeurs
        map.merge("A", 5, (ancienne, nouvelle) -> ancienne + nouvelle);
        System.out.println("merge('A', 5) : " + map);
    }
}
Sortie
putIfAbsent : {A=10}
computeIfAbsent : {A=10, B=5}
computeIfPresent : {A=110, B=5}
getOrDefault('Z') : 0
merge('A', 5) : {A=115, B=5}

7. Clés personnalisées — hashCode() et equals()

Règle fondamentale

Pour utiliser des objets personnalisés comme clés dans un HashMap, vous devez redéfinir correctement les méthodes hashCode() et equals(). Deux objets considérés comme égaux (equals() true) doivent avoir le même hashCode().

  Exemple 5 — Clé personnalisée

import java.util.HashMap;
import java.util.Objects;

class Client {
    private int id;
    private String email;

    public Client(int id, String email) {
        this.id = id;
        this.email = email;
    }

    // Getters, setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Client client = (Client) o;
        return id == client.id && Objects.equals(email, client.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, email);
    }

    @Override
    public String toString() {
        return "Client{id=" + id + ", email='" + email + "'}";
    }
}

public class TestClePersonnalisee {
    public static void main(String[] args) {
        HashMap<Client, String> commandes = new HashMap<>();

        Client c1 = new Client(1, "mostafa@email.com");
        Client c2 = new Client(2, "amal@email.com");

        commandes.put(c1, "Commande #1234");
        commandes.put(c2, "Commande #5678");

        // Recherche avec un objet équivalent
        Client recherche = new Client(1, "mostafa@email.com");
        String commande = commandes.get(recherche);

        System.out.println("Commande trouvée : " + commande);
    }
}
Sortie
Commande trouvée : Commande #1234

8. HashMap vs Hashtable vs ConcurrentHashMap

Caractéristique HashMap Hashtable ConcurrentHashMap
Synchronisation Non (non thread-safe) Oui (thread-safe) Oui (thread-safe, performances améliorées)
Clés/ valeurs nulles Oui (une clé null, plusieurs valeurs null) Non Non
Performance Élevée (pas de synchronisation) Faible (synchronisation globale) Bonne (synchronisation segmentée)
Ordre d'itération Non garanti Non garanti Non garanti
Depuis Java 1.2 Java 1.0 (dépréciée) Java 5
HashMap vs Hashtable

Hashtable est une ancienne classe synchronisée (thread-safe) mais déconseillée. Pour un accès concurrent, utilisez ConcurrentHashMap. Pour un usage mono-thread, HashMap est plus performant.

9. Exemple complet — Annuaire téléphonique

  Exemple 6 — Application d'annuaire

import java.util.HashMap;
import java.util.Scanner;

public class Annuaire {
    private HashMap<String, String> contacts;

    public Annuaire() {
        contacts = new HashMap<>();
    }

    public void ajouterContact(String nom, String telephone) {
        contacts.put(nom, telephone);
        System.out.println("Contact ajouté : " + nom + " → " + telephone);
    }

    public void rechercherParNom(String nom) {
        String telephone = contacts.get(nom);
        if (telephone != null) {
            System.out.println("" + nom + " : " + telephone);
        } else {
            System.out.println("Contact non trouvé : " + nom);
        }
    }

    public void supprimerContact(String nom) {
        String supprime = contacts.remove(nom);
        if (supprime != null) {
            System.out.println("Contact supprimé : " + nom);
        } else {
            System.out.println("Contact non trouvé : " + nom);
        }
    }

    public void modifierContact(String nom, String nouveauTelephone) {
        if (contacts.containsKey(nom)) {
            contacts.put(nom, nouveauTelephone);
            System.out.println("Contact modifié : " + nom + " → " + nouveauTelephone);
        } else {
            System.out.println("Contact non trouvé : " + nom);
        }
    }

    public void afficherTous() {
        if (contacts.isEmpty()) {
            System.out.println("Annuaire vide.");
            return;
        }
        System.out.println("\n=== ANNUAIRE ===");
        contacts.forEach((nom, tel) -> System.out.printf("  %s → %s%n", nom, tel));
        System.out.println("Total : " + contacts.size() + " contact(s)\n");
    }

    public static void main(String[] args) {
        Annuaire annuaire = new Annuaire();
        Scanner scanner = new Scanner(System.in);
        int choix;

        do {
            System.out.println("\n╔════════════════════════════════════╗");
            System.out.println("║         ANNUAIRE TÉLÉPHONIQUE       ║");
            System.out.println("╠════════════════════════════════════╣");
            System.out.println("║ 1. Ajouter un contact              ║");
            System.out.println("║ 2. Rechercher par nom              ║");
            System.out.println("║ 3. Supprimer un contact            ║");
            System.out.println("║ 4. Modifier un contact             ║");
            System.out.println("║ 5. Afficher tous les contacts      ║");
            System.out.println("║ 6. Quitter                         ║");
            System.out.println("╚════════════════════════════════════╝");
            System.out.print("Votre choix : ");
            choix = scanner.nextInt();
            scanner.nextLine();

            switch (choix) {
                case 1:
                    System.out.print("Nom : ");
                    String nom = scanner.nextLine();
                    System.out.print("Téléphone : ");
                    String tel = scanner.nextLine();
                    annuaire.ajouterContact(nom, tel);
                    break;
                case 2:
                    System.out.print("Nom : ");
                    nom = scanner.nextLine();
                    annuaire.rechercherParNom(nom);
                    break;
                case 3:
                    System.out.print("Nom : ");
                    nom = scanner.nextLine();
                    annuaire.supprimerContact(nom);
                    break;
                case 4:
                    System.out.print("Nom : ");
                    nom = scanner.nextLine();
                    System.out.print("Nouveau téléphone : ");
                    tel = scanner.nextLine();
                    annuaire.modifierContact(nom, tel);
                    break;
                case 5:
                    annuaire.afficherTous();
                    break;
                case 6:
                    System.out.println("Au revoir !");
                    break;
                default:
                    System.out.println("Choix invalide !");
            }
        } while (choix != 6);

        scanner.close();
    }
}

10. Exercice

Compteur de mots

Niveau : Intermédiaire

Créer un programme qui compte la fréquence d'apparition de chaque mot dans un texte.

Travail demandé

  1. Lire une phrase ou un texte saisi par l'utilisateur.
  2. Utiliser un HashMap<String, Integer> pour stocker chaque mot et son nombre d'occurrences.
  3. Ignorer la casse (convertir en minuscules).
  4. Ignorer la ponctuation (virgules, points, points d'exclamation, etc.).
  5. Afficher les mots triés par ordre alphabétique ou par fréquence décroissante.
  6. Afficher le nombre total de mots et le nombre de mots distincts.

  L'essentiel en bref

Synthèse — HashMap
  • HashMap stocke des paires clé/valeur avec recherche par clé en O(1) amorti.
  • Création : HashMap<K, V> map = new HashMap<>();
  • Insertion : put(key, value) — remplace si la clé existe déjà.
  • Récupération : get(key) — retourne null si la clé n'existe pas.
  • Vérification : containsKey(key) (efficace) ou containsValue(value) (lent).
  • Suppression : remove(key).
  • Clés personnalisées : doivent redéfinir hashCode() et equals().
  • Non thread-safe : utilisez ConcurrentHashMap pour la concurrence.
  • Une clé null autorisée, plusieurs valeurs null autorisées.
  • Parcours : keySet(), values(), entrySet() ou forEach() avec lambda.
Un peu d'histoire

Le concept de table de hachage a été inventé en 1953 par Hans Peter Luhn chez IBM. En Java, HashMap a été introduite dans Java 1.2 (1998) avec le Java Collections Framework. Java 8 a amélioré les performances en cas de collisions de hachage : les listes chaînées sont automatiquement converties en arbres équilibrés (arbres rouges-noirs) lorsqu'un bucket dépasse un seuil (8 éléments). Cette optimisation garantit une complexité O(log n) dans le pire cas, au lieu de O(n) auparavant.

Sortie
// La sortie apparaîtra ici…
Prêt · Ctrl+Entrée pour exécuter

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.