Gleichmäßige Verteilung (geringe Anzahl von Kollisionen).
Akzeptabler Worstcase, d.h. Maximum der Kollisionen pro Zelle sollte möglichst gering sein.
Verteilung sollte auch dann noch erhalten bleiben wenn anstelle von Modulo zur Beschränkung auf einen Wertebereich, eine Bitweise UND-Verknüpfung verwendet wird (wichtig für Performance).
Performance.
Keine Verwendung von Modulo-Operationen, da diese im Vergleich zu Bitweisen Operationen oder Additionen sehr langsam sind (gerade auf RISC Computern können diese die 100-fache Zeit benötigen).
Die in Knuth's "The Art of Computer Programming" (Volume 3 "Sorting and Searching", Kapitel 6.4) vorgestelle Hash-Funktion ist vielleicht eine der bekanntesten überhaupt. Dennoch ist ihre Verteilung nur mittelmäßig. Darüber hinaus verwendet Knuth eine Modulo Operation (mit einer Primzahl) zur Beschränkung auf ein Intervall, welches ihre Performance mindert und wegen der verwendeten Primzahl ein automatisches Vergrößern der Hashtabelle komplizierter macht.
unsigned long knuth_hash(unsigned char *str, int len) { unsigned long hash; for (hash=len; len--;) { hash = ((hash<<5)^(hash>>27))^*str++; } return hash; // % prime }
Kernigham und Ritchies Algorithmus ist eine einfache additive Hash-Funktion, die jedoch ungeeignet ist für seriöse Anwendungen.
unsigned long k_r(unsigned char *str) { unsigned long hash = 0; int c; while (c = *str++) hash += c; return hash; }
unsigned long djb2(unsigned char *str) { unsigned long hash = 5381; int c; while (c = *str++) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; }
Dieser Algorithmus wird in Sleepycat's Datenbank BDB (Berkeley DataBase) verwendet.
unsigned long sdbm(unsigned char *str) { unsigned long hash = 0; int c; while (c = *str++) hash = c + (hash << 6) + (hash << 16) - hash; return hash; }
unsigned long hashpjw(unsigned char* str) { unsigned long hash=0; unsigned long g; for(;*str;++str) { hash = (hash << 4)+ *str; if(g=hash&0xf0000000) { hash ^= g>>24; hash ^= g; } } return hash; }
Dieser Algorithmus wird in der aktuellen Version (3.04) von Objective Caml verwendet.
unsigned long ocaml_hash(unsigned char *str, int len) { unsigned long hash = 0; int i; for (i=0; i<len; i++) { hash = hash*19 + str[i]; } return hash; }
Dieser Algorithmus wird in der aktuellen Version (110.40) von SML/NJ (Standard ML/New Jersey) verwendet.
unsigned long sml_hash(unsigned char *str, int len) { unsigned long hash = 0; int i; for (i=0; i<len; i++) { hash = 33*hash + 720 + str[i]; } return hash; }
Dieser Algorithmus wird/wurde (?) in der STL (Standard Template Library) von C++ verwendet.
unsigned long stl_hash(unsigned char *str, int len) { unsigned long hash = 0; int i; for (i=0; i<len; i++) { hash = 5*hash + str[i]; } return hash; }
Dieser Algorithmus wird (?) in Java's SDK verwendet.
unsigned long java_hash(unsigned char *str, int len) { unsigned long hash = 0; int i; for (i=0; i<len; i++) { hash += ((unsigned long)s[i]*31)^(n-1-i); } return hash; }
FNV ist ein relativ neuer und guter Hash-Algorithmus. Er wurde von Phong Vo, Glenn Fowler entwickelt und von Landon Curt Noll verbessert.
"FNV hashes are designed to be fast while maintaining a low collision rate. The FNV speed allows one to quickly hash lots of data while maintaining a reasonable collision rate."
Mehr Information zum Algorithmus sowie eine Implementation in C ist im WWW unter folgender Adresse verfügbar: http://www.isthe.com/chongo/tech/comp/fnv/index.html.
Dies ist ein sehr schneller und guter Hash-Algorithmus. Er kommt ohne Multiplikation oder Modulo/Divison aus. Der berechneter Hash-Wert kann mittels Bitweisem UND auf einen kleineren Wertebereich eingeschränkt werden.
Mehr Information zum Algorithmus sowie eine Implementation in C ist im WWW unter folgenden Adressen verfügbar: http://burtleburtle.net/bob/hash/evahash.html und http://burtleburtle.net/bob/hash/index.html#lookup (Dateien lookupa.h und lookupa.c)
Im folgenden wird dieser Algorihmus NEW genannt.
Für den Vergleich wurde jede Hash-Funktion mit einem Testset von ungefähr 4,3 Millionen unterschiedlichen Wörtern "gefüttert" (keine binären Daten). Dabei wurden die Kollisionen bei einer n-Zellen großen Hash-Tabelle aufgezeichnet (wobei n eine 2er Potenz ist) und daraus die Standard-Abweichung, Anzahl der leeren Zellen sowie die maximale Kollisionsrate (einer Zelle) errechnet. Gerade Letzteres ist wichtig zur besseren Abschätzung eines realen Worstcases.
WarningEs wurde nicht beachtet, daß durch die Anwendung von Modulo mit einer 2er Potenz zur Einschränkung des Wertebereiches eine Verschlechterung der Hash-Funktion entstehen kann (bei den Hash-Funktionen wo Modulo angewandt wurde). Oft wird eine Primzahl bei einer Modulo Operation verwendet.
Deutlich lässt sich anhand der Diagramme erkennen, daß die besten Hash-Funktionen, der Fowler/Noll/Vo (FNV) sowie der Bob Jenkiens (NEW) Algorithmus sind. Für größere n jedoch kommt der SDBM Algorithmus sehr nahe an beide heran.
Getestet wurde die Berechnung von Wörter der Länge 1 bis 24.
Der SDBM Algorithmus ist sehr schnell, NEW liegt im Mittelfeld, FNV bildet das Schlusslicht!
Besonders positiv aufgefallen sind folgende drei Algorithmen:
FNV
NEW
SDBM
Wobei FNV von der Qualität der Hash-Werte am besten abschneidet (gerade für kleinere n), jedoch wegen der Performance (FNV ist der langsamste Algorithmus) ausscheidet.
Der NEW Algorithmus unterscheidet sich in der Qualität der Hash-Werte kaum vom FNV Algorithmus, jedoch ist er fast doppelt so schnell.
Der SDBM Algorithmus ist mehr als 2.5 mal so schnell wie NEW und ist in der Qualität nicht wesentlich schlechter als dieser. Besonders für große n sind alle drei Algorithmen in der Qualität nahezu gleich.