prev up next


Hashing


Hash-Tabelle mit Einstieg in Behälter

Die grundlegende Idee beim offenen Hashing ist es, die Records des Files auf mehrere Behälter (englisch: Bucket) aufzuteilen, die jeweils aus einer Folge von verzeigerten Blöcken bestehen. Es gibt eine Hash-Funktion h , die einen Schlüssel als Argument erhält und ihn auf die Bucket-Nummer abbildet, unter der der Block gespeichert ist, welcher das Record mit diesem Schlüssel enthält. Sei B die Anzahl der Buckets, sei V die Menge der möglichen Record-Schlüssel, dann gilt gewöhnlich |V| $\gg$ |B| .

Beispiel
für eine Hash-Funktion:
Fasse den Schlüssel v als k Gruppen von jeweils n Bits auf. Sei di die i -te Gruppe als natürliche Zahl interpretiert. Setze


Hash-Organisation vor Einfügen von Elasmosaurus


Hash-Organisation nach Einfügen von Elasmosaurus und Umbenennen

Im Bucket-Directory findet sich als h(v) -ter Eintrag der Verweis auf den Anfang einer Liste von Blöcken, unter denen das Record mit Schlüssel v zu finden ist. Abbildung 4.3 zeigt eine Hash-Tabelle, deren Hash-Funktion h die Personalnummer x durch h(x)  =  x mod 3 auf das Intervall [0..2] abbildet.

Falls B klein ist, kann sich das Bucket-Directory im Hauptspeicher befinden; andernfalls ist es über mehrere Blöcke im Hintergrundspeicher verteilt, von denen zunächst der zuständige Block geladen werden muß.

Jeder Block enthält neben dem Zeiger auf den Folgeblock noch jeweils 2 Bits pro Subblock (Platz für ein Record), die angeben, ob dieser Subblock leer (also beschreibbar), gefüllt (also lesbar) oder gelöscht (also nicht zum Lesen geeignet) ist. Gelöschte Records werden wegen der Gefahr hängender Zeiger bis zum generellen Aufräumen nicht wieder verwendet.

Zu einem Record mit Schlüssel v laufen die Operationen wie folgt ab:

Der Aufwand aller Operationen hängt davon ab, wie gleichmäßig die Hash-Funktion ihre Funktionswerte auf die Buckets verteilt und wie viele Blöcke im Mittel ein Bucket enthält. Im günstigsten Fall ist nur ein Directory-Zugriff und ein Datenblock-Zugriff erforderlich und ggf. ein Blockzugriff beim Zurückschreiben. Im ungünstigsten Fall sind alle Records in dasselbe Bucket gehasht worden und daher müssen ggf. alle Blöcke durchlaufen werden.

Beispiel
für offenes Hashing (übernommen aus Ullman, Kapitel 2):
Abbildungen 4.4 und 4.5 zeigen die Verwaltung von Dinosaurier-Records. Verwendet wird eine Hash-Funktion h , die einen Schlüssel v abbildet auf die Länge von v mod 5 . Pro Block können zwei Records mit Angaben zum Dinosaurier gespeichert werden sowie im Header des Blocks zwei Bits zum Frei/Belegt-Status der Subblocks. Abbildung 4.4 zeigt die Ausgangssituation. Nun werde Elasmosaurus (Hashwert = 2 ) eingefügt. Hierzu muß ein neuer Block für Bucket 2 angehängt werden. Dann werde Brontosaurus umgetauft in Apatosaurus. Da diese Änderung den Schlüssel berührt, muß das Record gelöscht und modifiziert neu eingetragen werden. Abbildung 4.5 zeigt das Ergebnis.

Bei geschickt gewählter Hash-Funktion werden sehr kurze Zugriffszeiten erreicht, sofern das Bucket-Directory der Zahl der benötigten Blöcke angepaßt ist. Bei statischem Datenbestand läßt sich dies leicht erreichen. Problematisch wird es bei dynamisch wachsendem Datenbestand. Um immer größer werdende Buckets zu vermeiden, muß von Zeit zu Zeit eine völlige Neuorganisation der Hash-Tabelle durchgeführt werden. Da dies sehr zeitaufwendig ist, wurde als Alternative das erweiterbare Hashing entwickelt.


prev up next