web-dev-qa-db-de.com

Implementierung einer HashMap

Wie erstelle ich eine Hashmap in C von Grund auf neu? Welche Parameter würden berücksichtigt und wie würden Sie die Hashmap testen, um festzustellen, wie gut sie ist? Wie in den Benchmark-Testfällen, die Sie ausführen müssen, bevor Sie sagen, dass Ihre Hash-Map vollständig ist.

42
Thunderboltz

Nun, wenn Sie die Grundlagen dahinter kennen, sollte es nicht zu schwer sein.

Im Allgemeinen erstellen Sie ein Array mit dem Namen "Buckets", das den Schlüssel und den Wert enthält, mit einem optionalen Zeiger zum Erstellen einer verknüpften Liste.

Wenn Sie mit einem Schlüssel auf die Hash-Tabelle zugreifen, verarbeiten Sie den Schlüssel mit einer benutzerdefinierten Hash-Funktion, die eine Ganzzahl zurückgibt. Sie nehmen dann den Modul des Ergebnisses und das ist die Position Ihres Array-Index oder "Bucket". Dann überprüfen Sie den nicht gehashten Schlüssel mit dem gespeicherten Schlüssel, und wenn er übereinstimmt, haben Sie den richtigen Ort gefunden.

Andernfalls ist eine "Kollision" aufgetreten. Sie müssen die verknüpfte Liste durchsuchen und die Schlüssel vergleichen, bis Sie übereinstimmen. (Beachten Sie, dass einige Implementierungen einen Binärbaum anstelle einer verknüpften Liste für Kollisionen verwenden.).

Sehen Sie sich diese schnelle Implementierung der Hash-Tabelle an:

https://attractivechaos.wordpress.com/2009/09/29/khash-h/

56
Unknown

Der beste Ansatz hängt von der erwarteten Schlüsselverteilung und der Anzahl der Kollisionen ab. Wenn relativ wenige Kollisionen zu erwarten sind, spielt es keine Rolle, welche Methode angewendet wird. Wenn eine große Anzahl von Kollisionen erwartet wird, hängt die zu verwendende Menge von den Kosten für das erneute Aufbereiten oder Prüfen im Vergleich zur Manipulation der erweiterbaren Bucket-Datenstruktur ab.

Aber hier ist ein Quellcode-Beispiel für eine Hashmap-Implementierung in C

5
TStamper

Das Hauptziel einer Hashmap besteht darin, einen Datensatz zu speichern und ihn mit einem eindeutigen Schlüssel zeitnah abzurufen. Es gibt zwei gängige Arten der Implementierung von Hashmaps:

  • Separate Verkettung: eine mit einer Reihe von Eimern (verknüpfte Listen)
  • Offene Adressierung: Ein einzelnes Array mit zusätzlichem Speicherplatz, sodass Indexkollisionen behoben werden können, indem der Eintrag in einem benachbarten Slot platziert wird.

Eine getrennte Verkettung ist vorzuziehen, wenn die Hashmap möglicherweise eine schlechte Hashfunktion aufweist. Es ist nicht wünschenswert, Speicher für möglicherweise nicht verwendete Slots vorab zuzuweisen, oder wenn Einträge eine variable Größe aufweisen. Diese Art von Hashmap funktioniert möglicherweise auch dann noch relativ effizient, wenn der Lastfaktor 1,0 überschreitet. Offensichtlich ist in jedem Eintrag zusätzlicher Speicher erforderlich, um verknüpfte Listenzeiger zu speichern.

Hashmaps mit offener Adressierung haben potenzielle Leistungsvorteile, wenn der Lastfaktor unter einem bestimmten Schwellenwert (im Allgemeinen etwa 0,7) gehalten wird und eine einigermaßen gute Hashfunktion verwendet wird. Dies liegt daran, dass sie potenzielle Cache-Ausfälle und viele kleine Speicherzuordnungen für eine verknüpfte Liste vermeiden und alle Vorgänge in einem zusammenhängenden, vorab zugewiesenen Array ausführen. Die Iteration durch alle Elemente ist auch billiger. Der Haken ist, dass Hashmaps mit offener Adressierung einer größeren Größe zugewiesen und erneut aufbereitet werden müssen, um einen idealen Ladefaktor beizubehalten. Andernfalls drohen erhebliche Leistungseinbußen. Es ist unmöglich, dass ihr Ladefaktor 1,0 überschreitet.

Einige wichtige Leistungskennzahlen, die beim Erstellen einer Hashmap ausgewertet werden müssen, sind:

  • Maximaler Belastungsfaktor
  • Durchschnittliche Kollisionszahl beim Einfügen
  • Verteilung von Kollisionen: Eine ungleichmäßige Verteilung (Clustering) kann auf eine schlechte Hash-Funktion hinweisen.
  • Relative Zeit für verschiedene Operationen: vorhandene und nicht vorhandene Einträge setzen, holen, entfernen.

Hier ist eine flexible Hashmap-Implementierung, die ich gemacht habe. Ich habe offene Adressierung und lineare Abtastung für die Kollisionsauflösung verwendet.

https://github.com/DavidLeeds/hashmap

3
Dave

Es gibt andere Mechanismen, um einen Überlauf zu handhaben, als die einfache verknüpfte Liste von Überlaufeinträgen, die z. Verschwendet viel Speicher.

Welcher Mechanismus zu verwenden ist, hängt unter anderem davon ab, ob Sie die Hash-Funktion auswählen und möglicherweise mehrere auswählen können (um beispielsweise Double-Hashing für die Behandlung von Kollisionen zu implementieren). Wenn Sie davon ausgehen, dass Sie häufig Elemente hinzufügen, oder wenn die Karte nach dem Füllen statisch ist. ob Sie beabsichtigen, Elemente zu entfernen oder nicht; ...

Der beste Weg, dies zu implementieren, besteht darin, zuerst über all diese Parameter nachzudenken und sie dann nicht selbst zu codieren, sondern eine ausgereifte vorhandene Implementierung auszuwählen. Google hat einige gute Implementierungen - z. http://code.google.com/p/google-sparsehash/

1
HD.