Die Essenz des GO-Sprachinterviews: Was ist der Zuweisungsprozess und der Löschprozess der Karte?

1. Wie ist der Zuweisungsprozess einer Karte?

Wie Sie anhand der Assemblersprache sehen können, ruft das Einfügen oder Ändern von Schlüsseln in die Karte letztendlich mapassigndie Funktion auf.

Tatsächlich ist die Syntax zum Einfügen oder Ändern von Schlüsseln dieselbe, mit der Ausnahme, dass der von ersterem betätigte Schlüssel nicht in der Karte vorhanden ist, während der von letzterem betätigte Schlüssel in der Karte vorhanden ist.

Mapassign verfügt über eine Reihe von Funktionen. Je nach Schlüsseltyp optimiert der Compiler sie in die entsprechende „Schnellfunktion“.

Schlüsselart einfügen
uint32 mapassign_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer
uint64 mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer
Zeichenfolge mapassign_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer

Wir untersuchen nur die allgemeinsten Zuweisungsfunktionen mapassign.

Insgesamt ist der Prozess sehr einfach: Berechnen Sie den Hash-Wert des Schlüssels, folgen Sie dem vorherigen Prozess entsprechend dem Hash-Wert, suchen Sie den zuzuweisenden Ort (möglicherweise zum Einfügen eines neuen Schlüssels oder zum Aktualisieren des alten Schlüssels) und weisen Sie ihn zu den entsprechenden Standort.

Der Quellcode ähnelt im Allgemeinen dem zuvor Erwähnten. Der Kern ist immer noch eine zweischichtige Schleife. Die äußere Schicht durchläuft den Bucket und seinen Überlauf-Bucket, und die innere Schicht durchquert die Zellen des gesamten Buckets. Aus Platzgründen werde ich die Kommentare zu diesem Teil des Codes nicht anzeigen. Wenn Sie interessiert sind, können Sie ihn lesen. Ich garantiere, dass Sie ihn verstehen können, nachdem Sie den Inhalt dieses Artikels verstanden haben.

Ich werde hier einige wichtige Punkte zu diesem Prozess erwähnen.

Die Funktion prüft zunächst die Flags der Karte. Wenn das Schreibflag-Bit von Flags zu diesem Zeitpunkt auf 1 gesetzt ist, bedeutet dies, dass andere Coroutinen „Schreib“-Vorgänge ausführen, was zu einer Panik des Programms führt. Dies zeigt auch, dass Map für Coroutinen unsicher ist.

Aus dem vorherigen Artikel wissen wir, dass die Erweiterung schrittweise erfolgt. Wenn sich die Karte im Erweiterungsprozess befindet und sich der Schlüssel in einem bestimmten Bucket befindet, muss sichergestellt werden, dass der diesem Bucket entsprechende alte Bucket die Migration abgeschlossen hat Verfahren. Das heißt, die Schlüssel im alten Bucket müssen in den neuen Bucket migriert (in zwei neue Buckets aufgeteilt) werden, bevor das Einfügen oder Aktualisieren im neuen Bucket durchgeführt werden kann.

Der oben erwähnte Vorgang wird an der Vorderseite der Funktion ausgeführt. Erst nachdem dieser Verschiebungsvorgang abgeschlossen ist, können wir die Adresse, an der der Schlüssel im neuen Bucket abgelegt werden soll, sicher lokalisieren und dann nachfolgende Vorgänge ausführen.

Jetzt ist es an der Zeit, den Ort zu finden, an dem der Schlüssel platziert werden soll. Es ist wichtig, Ihren Standort richtig zu finden. Bereiten Sie zwei Zeiger vor, einer ( inserti) zeigt auf die Position des Hash-Werts des Schlüssels im Tophash-Array und der andere ( insertk) zeigt auf die Position der Zelle (dh die Adresse, an der der Schlüssel schließlich platziert wird). Natürlich ist der Ort, der dem Wert entspricht, leicht zu finden. . Diese drei hängen tatsächlich zusammen. Die Indexposition im Tophash-Array bestimmt die Position des Schlüssels im gesamten Bucket (insgesamt 8 Schlüssel), und die Position des Werts muss die Länge von 8 Schlüsseln „kreuzen“.

Während der Schleife zeigen inserti und insertk jeweils auf die erste gefundene freie Zelle. Wenn der Schlüssel später nicht in der Karte gefunden wird, bedeutet dies, dass der Schlüssel in der ursprünglichen Karte nicht vorhanden ist, was bedeutet, dass ein neuer Schlüssel eingefügt werden muss. Die endgültige Schlüsselplatzierungsadresse ist die erste entdeckte „leere Position“ (Tophash ist leer).

Wenn alle 8 Schlüssel dieses Eimers voll sind, werden Sie nach dem Herausspringen aus der Schleife feststellen, dass sowohl inserti als auch insertk leer sind. Zu diesem Zeitpunkt müssen Sie einen Überlaufeimer hinter den Eimer hängen. Selbstverständlich ist es auch möglich, hinter dem Überlaufeimer einen weiteren Überlaufeimer aufzuhängen. Das bedeutet, dass zu viele Schlüssel-Hashes in diesem Bucket angekommen sind.

Bevor der Schlüssel offiziell platziert wird, muss der Status der Karte überprüft werden, um festzustellen, ob eine Erweiterung erforderlich ist. Wenn die Expansionsbedingungen erfüllt sind, wird ein Expansionsvorgang aktiv ausgelöst.

Danach muss der gesamte vorherige Prozess des Auffindens und Auffindens des Schlüssels erneut durchgeführt werden. Denn nach der Erweiterung hat sich die Verteilung der Schlüssel geändert.

Abschließend werden die kartenbezogenen Werte aktualisiert. Wenn ein neuer Schlüssel eingefügt wird, wird der Zählwert des Elementnummernfelds der Karte um 1 erhöht; das zu Beginn der Funktion gesetzte hashWritingSchreibflag wird gelöscht .

Darüber hinaus gibt es einen wichtigen Punkt anzusprechen. Das Finden der Position des Schlüssels und das Ausführen der zuvor erwähnten Zuweisungsoperation ist eigentlich nicht genau. mapassignAus dem Prototyp der Funktion können wir ersehen, dass die Funktion den Wertwert nicht übergibt. Wann wird also die Zuweisungsoperation ausgeführt?

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer

Die Antwort muss in Assemblersprache gefunden werden. Ich werde die Antwort direkt verraten. Wenn Sie interessiert sind, können Sie sie privat studieren. mapassignDer von der Funktion zurückgegebene Zeiger ist die Wertposition, die dem Schlüssel entspricht, auf den gezeigt wird. Mit der Adresse ist es einfach, Werte zuzuweisen.

2. Wie läuft der Löschvorgang der Karte ab?

Die zugrunde liegende Ausführungsfunktion des Schreibvorgangs ist mapdelete:

func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) 

Abhängig vom Schlüsseltyp wird der Löschvorgang in eine spezifischere Funktion optimiert:

Schlüsselart löschen
uint32 mapdelete_fast32(t *maptype, h *hmap, key uint32)
uint64 mapdelete_fast64(t *maptype, h *hmap, key uint64)
Zeichenfolge mapdelete_faststr(t *maptype, h *hmap, ky string)

Natürlich geht es uns nur um mapdeleteFunktionen. Zuerst wird das Flag h.flags überprüft. Wenn festgestellt wird, dass das Schreibflag 1 ist, gerät es direkt in Panik, da dies darauf hinweist, dass andere Coroutinen gleichzeitig Schreibvorgänge ausführen.

Berechnen Sie den Hash des Schlüssels und finden Sie den Bucket, in den er fällt. Überprüfen Sie diese Karte. Wenn sie sich im Erweiterungsprozess befindet, lösen Sie direkt einen Umzugsvorgang aus.

Der Löschvorgang ist ebenfalls eine zweistufige Schleife, und der Kern besteht darin, den spezifischen Speicherort des Schlüssels zu finden. Der Suchvorgang ist ähnlich, es wird Zelle für Zelle im Bucket durchsucht.

Nachdem Sie den entsprechenden Speicherort gefunden haben, führen Sie einen „Löschvorgang“ für den Schlüssel oder Wert durch:

// 对 key 清零
if t.indirectkey {
	*(*unsafe.Pointer)(k) = nil
} else {
	typedmemclr(t.key, k)
}

// 对 value 清零
if t.indirectvalue {
	*(*unsafe.Pointer)(v) = nil
} else {
	typedmemclr(t.elem, v)
}

Dekrementieren Sie abschließend den Zählwert um 1 und setzen Sie den Tophash-Wert an der entsprechenden Position Empty.

3. Warum sind die Schlüssel in der Karte ungeordnet?

Nachdem die Karte erweitert wurde, werden die Schlüssel verschoben. Nachdem die Schlüssel, die ursprünglich in denselben Eimer gefallen sind, verschoben wurden, fliegen einige Schlüssel weg (die Seriennummer des Eimers wird um 2^B erhöht). Der Durchlaufprozess besteht darin, den Bucket der Reihe nach zu durchlaufen und gleichzeitig die Schlüssel im Bucket der Reihe nach zu durchlaufen. Nach dem Umzug haben sich die Positionen der Schlüssel stark verändert: Einige Schlüssel fliegen in hohe Äste, andere bleiben still. Auf diese Weise können die Ergebnisse des Durchlaufens der Karte nicht in der ursprünglichen Reihenfolge vorliegen.

Wenn ich eine Karte mit hartem Code habe, führe ich natürlich keine Einfüge- oder Löschvorgänge für die Karte durch. Es liegt auf der Hand, dass jedes Mal, wenn eine solche Karte durchlaufen wird, eine feste Reihenfolge von Schlüssel-/Wertsequenzen zurückgegeben wird. Dies ist tatsächlich der Fall, aber Go eliminiert diese Praxis, da sie zu Missverständnissen unter Programmieranfängern führen kann, die denken, dass dies definitiv passieren wird, was in einigen Fällen zu großen Fehlern führen kann.

Natürlich macht Go einen besseren Job. Wenn wir die Karte durchqueren, beginnen wir nicht immer mit der Durchquerung von Bucket Nr. 0. Jedes Mal, wenn wir mit der Durchquerung von einem Bucket mit einer Zufallswert-Seriennummer beginnen, beginnen wir mit der Durchquerung von einer zufälligen Seriennummer dieses Buckets. Die Zelle beginnt mit der Traversierung. Auf diese Weise ist es unwahrscheinlich, dass eine feste Sequenz von Schlüssel-Wert-Paaren zurückgegeben wird, selbst wenn Sie über eine fest codierte Karte verfügen und diese nur durchlaufen.

Eine weitere Sache: Die Funktion „Das Ergebnis der Iteration der Karte ist ungeordnet“ wurde ab Go 1.0 hinzugefügt.

Supongo que te gusta

Origin blog.csdn.net/zy_dreamer/article/details/132800004
Recomendado
Clasificación