Geschichte der Fragestellung:
Slices sind eine der Schlüssel-Datenstrukturen in Go, die als Alternative zu Arrays fester Länge geschaffen wurden, um die Benutzerfreundlichkeit und die Speichereffizienz zu erhöhen. Sie ermöglichen eine flexible Arbeit mit Teilmengen von Arrays, haben jedoch eine Reihe von Feinheiten, die für leistungsstarken und sicheren Code wichtig sind.
Problem:
Viele Entwickler verstehen nicht, wie genau Slices aufgebaut sind: ein Slice ist nicht das eigentliche Array, sondern eine Struktur mit einem Zeiger auf ein Array, der Länge und Kapazität (capacity). Dies kann zu Speicherlecks, Fehlern beim Arbeiten mit Kopien und unerwarteten Effekten führen, wenn das zugrunde liegende Array verändert wird.
Lösung:
Ein Slice ist ein Typ:
type slice struct { ptr unsafe.Pointer len int cap int }
Beim Erweitern eines Slices mit append() kann eine Neuzuteilung des zugrunde liegenden Arrays stattfinden, und alle bisherigen Referenzen auf das alte Array bleiben gültig, zeigen jedoch auf alte Daten. Unkenntnis über dieses Merkmal führt zu Fehlern und Speicherlecks.
Beispiel für eine korrekte Speicherzuweisung und Kopie:
src := []int{1,2,3,4,5} dst := make([]int, len(src)) copy(dst, src)
Ein Slice, das mit [:] erstellt wurde, teilt sich das zugrunde liegende Array, und ihre Modifikation wirkt sich gegenseitig aus, wenn kein copy durchgeführt wird.
Wichtige Merkmale:
Was passiert, wenn die Kapazität des Slices bei Verwendung von append überschritten wird, wenn andere Slices auf dasselbe Array verweisen?
append, das die Kapazität überschreitet, erstellt ein zugrunde liegendes Array mit neuer Speicherung im Speicher, und nur dieses Slice verweist auf das neue Array, während die anderen auf das alte verweisen. Dies ist eine häufige Ursache für Dateninkonsistenzen.
Warum ist es wichtig, kleine, lange lebende Slices, die aus einem großen Array stammen, nicht zu speichern?
Selbst wenn das Slice sehr klein ist, speichert sein Zeiger einen Verweis auf das gesamte zugrunde liegende Array, was dazu führen kann, dass das große Array im Speicher gehalten wird und ein Speicherleck entsteht.
Was passiert, wenn man ein Array außerhalb seiner Grenzen slicet?
Es tritt ein panic auf: runtime error: slice bounds out of range.
Eine Funktion liest eine große Datei in ein Byte-Array und gibt die ersten 100 Elemente als Slice zurück. Dieses Slice wird dann lange gespeichert, während der gesamte Speicher für das große Array im GC bleibt.
Vorteile:
Nachteile:
Sofort nach Erhalt des Slices wird der benötigte Abschnitt in ein neues Slice mit make und copy kopiert. Das alte Array wird sofort vergessen, der GC gibt den Speicher frei.
Vorteile:
Nachteile: