Heim > Backend-Entwicklung > Golang > Arrays vs. Slices in Go: Die Funktionsweise „unter der Haube' visuell verstehen

Arrays vs. Slices in Go: Die Funktionsweise „unter der Haube' visuell verstehen

Patricia Arquette
Freigeben: 2024-12-21 18:27:15
Original
275 Leute haben es durchsucht

Arrays vs Slices in Go: Understanding the

Haben Sie schon einmal versucht, für eine Reise zu packen, ohne zu wissen, wie lange Sie dort bleiben werden? Genau das passiert, wenn wir Daten in Go speichern. Manchmal, etwa beim Packen für einen Wochenendausflug, wissen wir genau, wie viele Dinge wir verstauen müssen; In anderen Fällen, beispielsweise wenn wir für eine Reise packen und sagen: „Ich komme zurück, wenn ich bereit bin“, tun wir das nicht.

Lassen Sie uns tief in die Welt der Go-Arrays eintauchen und die Interna anhand einfacher Illustrationen aufschlüsseln. Wir werden Folgendes untersuchen:

  1. Speicherlayouts
  2. Wachstumsmechanismen
  3. Referenzsemantik
  4. Auswirkungen auf die Leistung

Am Ende dieser Lektüre werden Sie mithilfe von Beispielen aus der Praxis und Speicherdiagrammen verstehen können, wann Arrays und wann Slices zu verwenden sind

Arrays: Der Container mit fester Größe?

Stellen Sie sich ein Array als einen einzelnen Speicherblock vor, in dem jedes Element nebeneinander sitzt, wie eine Reihe perfekt angeordneter Kästchen.

Wenn Sie var-Zahlen [5]int deklarieren, reserviert Go genau genug zusammenhängenden Speicher, um 5 ganze Zahlen aufzunehmen, nicht mehr und nicht weniger.

Arrays vs Slices in Go: Understanding the

Da sie über zusammenhängenden festen Speicher verfügen, kann die Größe während der Laufzeit nicht geändert werden.

func main() {
    // Zero-value initialization
    var nums [3]int    // Creates [0,0,0]

    // Fixed size
    nums[4] = 1       // Runtime panic: index out of range

    // Sized during compilation
    size := 5
    var dynamic [size]int  // Won't compile: non-constant array bound
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Arrays vs Slices in Go: Understanding the

Die Größe ist Teil des Array-Typs. Das bedeutet, dass [5]int und [6]int völlig unterschiedliche Typen sind, genau wie int und string unterschiedlich sind.

func main() {
    // Different types!
    var a [5]int
    var b [6]int

    // This won't compile
    a = b // compile error: cannot use b (type [6]int) as type [5]int

    // But this works
    var c [5]int
    a = c // Same types, allowed
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Warum wird Array standardmäßig kopiert?

Wenn Sie Arrays in Go zuweisen oder übergeben, werden standardmäßig Kopien erstellt. Dies stellt die Datenisolation sicher und verhindert unerwartete Mutationen.

Arrays vs Slices in Go: Understanding the

func modifyArrayCopy(arr [5]int) {
    arr[0] = 999    // Modifies the copy, not original
}

func modifyArray(arr *[5]int){
    arr[0] = 999  // Modifies the original, since reference is passed
}

func main() {
    numbers := [5]int{1, 2, 3, 4, 5}

    modifyArrayCopy(numbers)
    fmt.Println(numbers[0])  // prints 1, not 999

    modifyArray(&numbers)
    fmt.Println(numbers[0])  // prints 999
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Scheiben

Okay, Sie können also nicht vardynamic [size]int verwenden, um die dynamische Größe festzulegen. Hier kommt Slice ins Spiel.

Scheiben unter der Haube

Der Zauber liegt darin, wie es diese Flexibilität beibehält und gleichzeitig den Betrieb schnell hält.

Jedes Slice in Go besteht aus drei kritischen Komponenten:

Arrays vs Slices in Go: Understanding the

type slice struct {
    array unsafe.Pointer // Points to the actual data
    len   int           // Current number of elements
    cap   int           // Total available space
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Was ist unsicher.Pointer??

Der unsafe.Pointer ist Gos Methode zum Umgang mit Rohspeicheradressen ohne Typsicherheitseinschränkungen. Es ist „unsicher“, da es das Typsystem von Go umgeht und eine direkte Speichermanipulation ermöglicht.

Betrachten Sie es als das Äquivalent von Go zum Void-Zeiger von C.

Was ist das für ein Array?

Wenn Sie ein Slice erstellen, weist Go einen zusammenhängenden Speicherblock im Heap zu (im Gegensatz zu Arrays), der als Backing-Array bezeichnet wird. Jetzt zeigt das Array in der Slice-Struktur auf den Anfang dieses Speicherblocks.

Das Array-Feld verwendet unsafe.Pointer, weil:

  1. Es muss auf Rohspeicher ohne Typinformationen verweisen
  2. Es ermöglicht Go, Slices für jeden Typ T zu implementieren, ohne für jeden Typ separaten Code zu generieren.

Der dynamische Mechanismus von Slice

Lassen Sie uns versuchen, eine Intuition für den eigentlichen Algorithmus unter der Haube zu entwickeln.

Arrays vs Slices in Go: Understanding the

Wenn wir unserer Intuition folgen, können wir zwei Dinge tun:

  1. Wir könnten so viel Platz reservieren und ihn nach Bedarf nutzen
    Vorteile: Bewältigt wachsende Bedürfnisse bis zu einem bestimmten Punkt
    Nachteile: Speicherverschwendung, praktisch könnte das Limit erreicht werden

  2. Wir könnten zunächst eine zufällige Größe festlegen und beim Anhängen der Elemente können wir den Speicher bei jedem Anhängen neu zuweisen
    Vorteile: Behandelt den vorherigen Fall, kann je nach Bedarf erweitert werden
    Nachteile: Neuzuweisung ist teuer und bei jedem Anhang wird es schlimmer

Wir können die Neuzuweisung nicht vermeiden, denn wenn die Kapazität erreicht ist, muss man wachsen. Wir können die Neuzuweisung minimieren, sodass die Kosten für nachfolgende Einfügungen/Anhänge konstant sind (O(1)). Dies wird als fortgeführte Anschaffungskosten bezeichnet.

Wie können wir das angehen?

bis zur Go-Version v1.17 wurde folgende Formel verwendet:

func main() {
    // Zero-value initialization
    var nums [3]int    // Creates [0,0,0]

    // Fixed size
    nums[4] = 1       // Runtime panic: index out of range

    // Sized during compilation
    size := 5
    var dynamic [size]int  // Won't compile: non-constant array bound
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

ab Go-Version v1.18:

func main() {
    // Different types!
    var a [5]int
    var b [6]int

    // This won't compile
    a = b // compile error: cannot use b (type [6]int) as type [5]int

    // But this works
    var c [5]int
    a = c // Same types, allowed
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Da das Verdoppeln einer großen Scheibe Speicherverschwendung ist, nimmt der Wachstumsfaktor mit zunehmender Scheibengröße ab.

Lassen Sie uns die Nutzungsperspektive besser verstehen

Arrays vs Slices in Go: Understanding the

func modifyArrayCopy(arr [5]int) {
    arr[0] = 999    // Modifies the copy, not original
}

func modifyArray(arr *[5]int){
    arr[0] = 999  // Modifies the original, since reference is passed
}

func main() {
    numbers := [5]int{1, 2, 3, 4, 5}

    modifyArrayCopy(numbers)
    fmt.Println(numbers[0])  // prints 1, not 999

    modifyArray(&numbers)
    fmt.Println(numbers[0])  // prints 999
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Lassen Sie uns einige Elemente zu unserem Slice hinzufügen

type slice struct {
    array unsafe.Pointer // Points to the actual data
    len   int           // Current number of elements
    cap   int           // Total available space
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Da wir Kapazitäten haben (5) > Länge (3), Los:

Verwendet vorhandenes Backing-Array
Platziert 10 auf Index 3
Erhöht die Länge um 1

// Old growth pattern
capacity = oldCapacity * 2  // Simple doubling
Nach dem Login kopieren
Nach dem Login kopieren

Lass uns ans Limit gehen

// New growth pattern
if capacity < 256 {
    capacity = capacity * 2
} else {
    capacity = capacity + capacity/4  // 25% growth
}
Nach dem Login kopieren
Nach dem Login kopieren

Ups! Jetzt, wo wir unsere Kapazitäten erreicht haben, müssen wir wachsen. Folgendes passiert:

  1. Berechnet die neue Kapazität (oldCap < 256, verdoppelt sich also auf 10)
  2. Ordnet ein neues Backing-Array zu (eine neue Speicheradresse, z. B. 300)
  3. Kopiert vorhandene Elemente in ein neues Backing-Array
  4. Fügt neues Element hinzu
  5. Aktualisiert den Slice-Header

Arrays vs Slices in Go: Understanding the

func main() {
    // Zero-value initialization
    var nums [3]int    // Creates [0,0,0]

    // Fixed size
    nums[4] = 1       // Runtime panic: index out of range

    // Sized during compilation
    size := 5
    var dynamic [size]int  // Won't compile: non-constant array bound
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Was passiert, wenn es eine große Scheibe ist?

func main() {
    // Different types!
    var a [5]int
    var b [6]int

    // This won't compile
    a = b // compile error: cannot use b (type [6]int) as type [5]int

    // But this works
    var c [5]int
    a = c // Same types, allowed
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Da die Kapazität 256 beträgt, verwendet Go die Wachstumsformel nach 1,18:

Neue Kapazität = oldCap oldCap/4
256 256/4 = 256 64 = 320

func modifyArrayCopy(arr [5]int) {
    arr[0] = 999    // Modifies the copy, not original
}

func modifyArray(arr *[5]int){
    arr[0] = 999  // Modifies the original, since reference is passed
}

func main() {
    numbers := [5]int{1, 2, 3, 4, 5}

    modifyArrayCopy(numbers)
    fmt.Println(numbers[0])  // prints 1, not 999

    modifyArray(&numbers)
    fmt.Println(numbers[0])  // prints 999
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Warum Referenzsemantik?

  1. Leistung: Das Kopieren großer Datenstrukturen ist teuer
  2. Speichereffizienz: Vermeidung unnötiger Datenduplizierung
  3. Gemeinsame Datenansichten ermöglichen: Mehrere Slices können auf dasselbe Backing-Array verweisen
type slice struct {
    array unsafe.Pointer // Points to the actual data
    len   int           // Current number of elements
    cap   int           // Total available space
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

So sehen die Slice-Header aus:

// Old growth pattern
capacity = oldCapacity * 2  // Simple doubling
Nach dem Login kopieren
Nach dem Login kopieren

Nutzungsmuster und Vorsichtsmaßnahmen für Slice

Versehentliche Aktualisierungen

Da Slice Referenzsemantik verwendet, werden keine Kopien erstellt, die bei Unachtsamkeit zu einer versehentlichen Mutation zum Original-Slice führen könnten.

// New growth pattern
if capacity < 256 {
    capacity = capacity * 2
} else {
    capacity = capacity + capacity/4  // 25% growth
}
Nach dem Login kopieren
Nach dem Login kopieren

Teurer Anhängevorgang

numbers := make([]int, 3, 5) // length=3 capacity

// Memory Layout after creation:
Slice Header:
{
    array: 0xc0000b2000    // Example memory address
    len:   3
    cap:   5
}

Backing Array at 0xc0000b2000:
[0|0|0|unused|unused]
Nach dem Login kopieren

Kopieren vs. Anhängen

numbers = append(numbers, 10)
Nach dem Login kopieren

Arrays vs Slices in Go: Understanding the

Lassen Sie uns das mit einem klaren Auswahlleitfaden abschließen:

? Wählen Sie Arrays, wenn:

  1. Sie kennen die genaue Größe im Voraus
  2. Arbeiten mit kleinen, festen Daten (wie Koordinaten, RGB-Werte)
  3. Leistung ist entscheidend und Daten passen auf den Stapel
  4. Sie möchten Typsicherheit mit Größe

? Wählen Sie Slices, wenn:

  1. Größe kann sich ändern
  2. Arbeiten mit dynamischen Daten
  3. Benötigen Sie mehrere Ansichten derselben Daten
  4. Verarbeitung von Streams/Sammlungen

? Schauen Sie sich das notion-to-md-Projekt an! Es handelt sich um ein Tool, das Notion-Seiten in Markdown konvertiert und sich perfekt für Inhaltsersteller und Entwickler eignet. Treten Sie unserer Discord-Community bei.

Das obige ist der detaillierte Inhalt vonArrays vs. Slices in Go: Die Funktionsweise „unter der Haube' visuell verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage