Rust HashMap: Wie man Daten effizient sucht und einfügt

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Wie man Daten in HashMap in Rust effizient sucht und einfügt: Ein Blick unter die Haube

\n\n

Haben Sie jemals Code in Rust geschrieben und gedacht: "Wie kann ich gleichzeitig prüfen, ob ein Schlüssel in HashMap vorhanden ist, und wenn nicht, einen neuen Wert einfügen?" Diese Frage quält Tausende von Entwicklern auf Stack Overflow. Und das zu Recht: Eine naive Lösung kann die Leistung Ihrer Anwendung zerstören. Heute geben wir nicht nur eine Antwort – wir tauchen in die Rust HashMap ein und sehen, wie sie aufgebaut ist. Sie erfahren, warum get und insert einzeln der Weg zum Schmerz sind und wie man die entry API richtig verwendet.

\n\n

Dieser Artikel richtet sich an diejenigen, die bereits mit den Grundlagen von Rust vertraut sind, aber tiefer verstehen möchten, wie Collections unter der Haube funktionieren. Los geht's!

\n\n

Das Problem: Zwei Aufrufe – doppelte Arbeit

\n\n

Stellen Sie sich eine typische Aufgabe vor: Sie haben eine HashMap, in der Sie die Anzahl der Vorkommen von Wörtern in einem Text zählen müssen. Ein naiver Ansatz sieht so aus:

\n\n
use std::collections::HashMap;\n\nfn count_words_naive(words: &[&str]) -> HashMap<String, u32> {\n    let mut map = HashMap::new();\n    \n    for word in words {\n        if map.contains_key(*word) {\n            let count = map.get(*word).unwrap();\n            map.insert(word.to_string(), count + 1);\n        } else {\n            map.insert(word.to_string(), 1);\n        }\n    }\n    \n    map\n}\n
\n\n

Was ist hier schlecht? Wir greifen für jedes Wort zweimal auf die HashMap zu: zuerst contains_key, dann get und dann noch insert. Jeder Aufruf bedeutet Berechnung des Hashs, Suche im Bucket und Vergleich der Schlüssel. Bei einem kurzen Text fällt das nicht auf, aber bei Millionen von Einträgen ist es eine Katastrophe.

\n\n

Unter der Haube: Wie HashMap in Rust aufgebaut ist

\n\n

Um zu verstehen, warum das schlecht ist, schauen wir uns an, wie HashMap in Rust funktioniert. Grundlage ist eine Hash-Tabelle mit offener Adressierung (Robin Hood Hashing). Hier sind die wichtigsten Punkte:

\n\n
    \n
  • Hashing: Der Schlüssel wird mit einer Hash-Funktion (standardmäßig SipHash-2-4) in einen Hash-Code umgewandelt.
  • \n
  • Buckets: Der Hash-Code bestimmt den Index in einem Array von Buckets. In Rust ist jeder Bucket ein Slot, der leer, belegt oder gelöscht sein kann.
  • \n
  • Kollisionen: Wenn zwei verschiedene Schlüssel in denselben Bucket fallen, versucht Robin Hood Hashing, einem "reicheren" Nachbarn den Platz zu "stehlen", um die Suchdistanzen auszugleichen.
  • \n
  • Resizing: Wenn die Tabelle voll ist (Load Factor ~0.9), wird sie um das Doppelte vergrößert und alle Elemente werden neu gehasht.
  • \n
\n\n

Stellen Sie sich nun vor: Jeder Aufruf von get oder insert ist ein vollständiger Zyklus: Hashing, Indexsuche, Schlüsselvergleich (möglicherweise mehrmals aufgrund von Kollisionen). Zwei Aufrufe = doppelte Arbeit. Und wenn zwischen den Aufrufen ein Resizing stattfindet? Dreifache Arbeit!

\n\n

Die Lösung: Entry API – Ihr Rettungsanker

\n\n

Rust bietet eine elegante Lösung – die Entry API. Dies ist ein Mechanismus, der es ermöglicht, Suche und ggf. Einfügen in einem Durchgang durchzuführen. So sieht der korrekte Code zum Zählen von Wörtern aus:

\n\n
use std::collections::HashMap;\n\nfn count_words_efficient(words: &[&str]) -> HashMap<String, u32> {\n    let mut map = HashMap::new();\n    \n    for word in words {\n        let entry = map.entry(word.to_string());\n        *entry.or_insert(0) += 1;\n    }\n    \n    map\n}\n
\n\n

Kurz, schön, effizient. Aber wie funktioniert das unter der Haube? Lassen Sie es uns Schritt für Schritt durchgehen.

\n\n

Schritt 1: Die Methode entry()

\n\n

Der Aufruf map.entry(key) gibt eine Entry-Enumeration zurück, die sich in zwei Zuständen befinden kann:

\n\n
    \n
  • Entry::Occupied – der Schlüssel existiert bereits in der Tabelle.
  • \n
  • Entry::Vacant – der Schlüssel existiert nicht, und wir haben einen bereiten Platz zum Einfügen.
  • \n
\n\n

Wichtig: Dafür

Blogs

Buchempfehlungen