Comment fonctionne une table de hachage en interne en C#  ?

16 vues
En C#, une HashTable associe des clés à des valeurs. Pour stocker un élément, la clé est hachée pour déterminer lindex où la valeur sera stockée. Les clés doivent être immuables et uniques au sein de la HashTable. Par exemple, une classe Person pourrait être stockée en utilisant le nom de famille comme clé.
Commentaire 0 j’aime

Comprendre le fonctionnement interne d'une HashTable en C

En C#, la classe HashTable, faisant partie de l'espace de noms System.Collections, offre un moyen efficace de stocker et de récupérer des données sous forme de paires clé-valeur. Son fonctionnement interne, bien que masqué derrière une interface simple, repose sur un mécanisme ingénieux : le hachage. Comprendre ce mécanisme permet de mieux appréhender les performances et les limitations de cette structure de données.

L'essence du Hachage : Transformer les Clés en Indices

Au cœur de la HashTable réside la fonction de hachage. Son rôle principal est de transformer une clé (de n'importe quel type .NET) en un entier unique, appelé code de hachage. Ce code de hachage, bien qu'unique, n'est pas directement utilisé comme index. Il est ensuite manipulé pour le ramener dans les limites de la taille du tableau interne de la HashTable. C'est cet indice qui déterminera l'emplacement où la paire clé-valeur sera stockée.

La fonction GetHashCode() définie sur l'objet clé est utilisée pour générer le code de hachage. Il est crucial que cette fonction respecte certaines règles :

  • Consistance : Pour une même clé, GetHashCode() doit toujours retourner la même valeur.
  • Égalité : Si deux objets sont égaux (au sens de la méthode Equals()), leurs codes de hachage doivent être identiques.

Le Tableau Interne et la Gestion des Collisions

La HashTable utilise un tableau interne pour stocker les paires clé-valeur. Chaque cellule du tableau peut potentiellement contenir une paire. Cependant, il est inévitable que plusieurs clés différentes puissent générer le même indice après la manipulation du code de hachage. C'est ce qu'on appelle une collision.

Pour gérer les collisions, la HashTable en C# utilise généralement une technique appelée chaînage séparé. Cela signifie que chaque cellule du tableau ne contient pas directement la paire clé-valeur, mais plutôt une liste chaînée de paires qui partagent le même indice. Lorsqu'une collision se produit, la nouvelle paire est simplement ajoutée à la liste chaînée existante dans la cellule correspondante.

L'Algorithme d'Ajout (Add) d'un Élément

  1. Calcul du code de hachage : La méthode GetHashCode() de la clé est appelée pour obtenir son code de hachage.
  2. Détermination de l'indice : Le code de hachage est transformé pour obtenir un indice valide dans le tableau interne. Cette transformation utilise généralement l'opérateur modulo (%) avec la taille du tableau.
  3. Recherche de l'emplacement : La cellule du tableau correspondant à l'indice est localisée.
  4. Gestion des collisions :
    • Si la cellule est vide, une nouvelle liste chaînée est créée et la paire clé-valeur est ajoutée à cette liste.
    • Si la cellule contient déjà une liste chaînée, la liste est parcourue pour vérifier si une clé identique existe déjà.
      • Si une clé identique est trouvée (en utilisant la méthode Equals() de la clé), la valeur associée est mise à jour.
      • Sinon, la nouvelle paire clé-valeur est ajoutée à la fin de la liste chaînée.

L'Algorithme de Récupération (Get) d'un Élément

  1. Calcul du code de hachage : La méthode GetHashCode() de la clé recherchée est appelée.
  2. Détermination de l'indice : Le code de hachage est transformé pour obtenir l'indice correspondant.
  3. Recherche dans la liste chaînée : La liste chaînée stockée dans la cellule du tableau à l'indice déterminé est parcourue.
  4. Comparaison des clés : Chaque clé dans la liste chaînée est comparée à la clé recherchée en utilisant la méthode Equals().
  5. Retour de la valeur : Si une clé correspondante est trouvée, la valeur associée est retournée. Si aucune clé correspondante n'est trouvée après avoir parcouru toute la liste chaînée, null (ou la valeur par défaut du type de la valeur) est généralement retourné.

Redimensionnement du Tableau

Pour maintenir des performances optimales, la HashTable peut redimensionner son tableau interne lorsque le nombre d'éléments stockés dépasse un certain seuil (appelé facteur de charge). Ce redimensionnement implique la création d'un nouveau tableau plus grand et la réinsertion de toutes les paires clé-valeur du tableau original dans le nouveau tableau. Ce processus peut être coûteux en termes de performance, mais il est essentiel pour éviter des listes chaînées trop longues, ce qui dégraderait les performances de recherche.

Considérations importantes

  • Immutabilité des clés : Il est crucial que les clés utilisées dans une HashTable soient immuables. Si une clé est modifiée après son ajout à la HashTable, son code de hachage peut changer, et elle ne sera plus retrouvée à l'emplacement correct. Cela peut conduire à des erreurs imprévisibles.
  • Performances : En moyenne, les opérations d'ajout, de suppression et de recherche dans une HashTable ont une complexité temporelle de O(1) (temps constant). Cependant, dans le pire des cas (beaucoup de collisions), la complexité peut devenir O(n) (temps linéaire), où n est le nombre d'éléments dans la liste chaînée la plus longue.
  • ConcurrentDictionary : Pour les environnements multithreadés, il est fortement recommandé d'utiliser ConcurrentDictionary (dans l'espace de noms System.Collections.Concurrent) à la place de HashTable. ConcurrentDictionary offre une thread-safety intégrée, évitant ainsi les problèmes de concurrence.

En conclusion

La HashTable en C# est une structure de données puissante pour stocker et récupérer des données rapidement grâce au hachage. Comprendre son fonctionnement interne, notamment la gestion des collisions et le redimensionnement, permet d'optimiser son utilisation et de choisir la structure de données la plus appropriée en fonction des besoins spécifiques de l'application. Il est cependant important de garder à l'esprit les restrictions sur l'immutabilité des clés et de considérer ConcurrentDictionary pour les environnements multithreadés.