Es fehlt, wie die beiden Datenstrukturen mit Hash-Kollisionen umgehen. Die Bloom-Filter speichern nicht die tatsächlichen Werte, daher entspricht der erforderliche Speicherplatz der konstanten Größe des angegebenen Arrays. Wenn Sie stattdessen einen herkömmlichen Hash verwenden, wird versucht, alle von Ihnen angegebenen Werte zu speichern, sodass dieser mit der Zeit wächst.
Betrachten Sie eine vereinfachte Hash-Funktion (nur als Beispiel!) f(x) = x % 2
. Nun Sie geben die folgenden Zahlen: 2, 3, 4, 5, 6, 7
.
Standard-Hash: Die angegebenen Werte werden gehasht und es kommt zu vielen Kollisionen aufgrund von f(2) = f(4) = f(6) = 0
und f(3) = f(5) = f(7) = 1
. Der Hash speichert jedoch alle diese Werte und kann Ihnen mitteilen, dass diese 8
nicht in ihm gespeichert sind. Wie macht es das? Es verfolgt Kollisionen und speichert alle Werte mit demselben Hash-Wert. Wenn Sie es abfragen, vergleicht es Ihre Abfrage zusätzlich. Lassen Sie uns also die Map nach 8
: abfragen f(8) = 0
, damit sie in einen Eimer schaut, in den wir bereits eingefügt haben, 2, 4, 6
und 3 Vergleiche durchführen muss, um Ihnen mitzuteilen, dass dies 8
nicht Teil der Eingabe war.
Bloom-Filter: Normalerweise wird jeder Eingangswert mit k
verschiedenen Hash-Funktionen verglichen . Nehmen wir der Einfachheit halber an, wir verwenden nur die einzelne Hash-Funktion f
. Wir brauchen dann ein Array mit 2 Werten und wenn wir auf die Eingabe stoßen 2
, bedeutet dies, dass f(2) = 0
wir den Array-Wert an der Position 0
auf den Wert setzen 1
. Das gleiche passiert für 4
und 6
. In ähnlicher Weise setzen die Eingänge 3, 5, 7
jeweils die Array-Position 1
auf einen Wert 1
. Nun fragen wir ab, ob 8
ein Teil der Eingabe war: f(8) = 0
und das Array an der Position 0
ist 1
, so dass der Bloom-Filter fälschlicherweise behauptet, dass dies 8
tatsächlich ein Teil der Eingabe war.
Um ein bisschen realistischer zu werden, nehmen wir an, dass wir eine zweite Hash-Funktion hinzufügen g(x) = x % 10
. Damit wird der Eingangswert 2
führt zu zwei Hash - Werten f(2) = 0
und g(2) = 2
und die beiden entsprechenden Array - Positionen gesetzt werden 1
. Natürlich sollte das Array jetzt mindestens so groß sein 10
. Aber wenn wir danach fragen 8
, überprüfen wir das Array an der Position 8
aufgrund von g(8) = 8
, und diese Position bleibt bestehen 0
. Aus diesem Grund verringern zusätzliche Hash-Funktionen die Anzahl der Fehlalarme.
Vergleich: Der Bloom-Filter verwendet k
Hash-Funktionen, was bedeutet, dass auf k
zufällige Array-Positionen zugegriffen wird. Aber diese Zahl ist genau. Der Hash garantiert Ihnen stattdessen nur eine amortisierte konstante Zugriffszeit, kann jedoch abhängig von der Art Ihrer Hash-Funktion und der eingegebenen Daten aus der Generierung ausbleiben. So ist es in der Regel schneller, mit Ausnahme der de-generierten Fälle.
Sobald Sie jedoch eine Hash-Kollision haben, muss der Standard-Hash die Gleichheit der gespeicherten Werte mit dem Abfragewert vergleichen. Diese Gleichheitsprüfung kann beliebig teuer sein und wird bei einem Bloom-Filter niemals auftreten.
In Bezug auf den Platz ist der Bloom-Filter konstant, da nie mehr Speicher als das angegebene Array benötigt wird. Andererseits wächst der Hash dynamisch und kann viel größer werden, da kollidierte Werte nachverfolgt werden müssen.
Kompromiss: Jetzt, da Sie wissen, was billig ist und was nicht und unter welchen Umständen, sollten Sie in der Lage sein, den Kompromiss zu sehen. Bloom-Filter eignen sich hervorragend, wenn Sie sehr schnell erkennen möchten, dass ein Wert zuvor gesehen wurde, aber mit falsch positiven Ergebnissen leben können. Auf der anderen Seite können Sie die Hash-Map wählen, wenn Sie eine garantierte Korrektheit zu dem Preis wünschen, dass Sie Ihre Laufzeit nicht genau einschätzen können, aber gelegentlich degenerierte Fälle akzeptieren, die möglicherweise viel langsamer als der Durchschnitt sind.
Wenn Sie sich in einer Umgebung mit begrenztem Speicher befinden, möchten Sie möglicherweise Bloom-Filter für die Garantie der Speichernutzung bevorzugen.