Historia pytania:
Slice'y — to jedna z kluczowych dynamicznych struktur w Go, która pojawiła się jako alternatywa dla tablic o stałej długości w celu zwiększenia wygody i rentowności pamięci. Umożliwiają elastyczną pracę z podzbiorami tablic, ale mają szereg szczegółów, które są ważne dla wydajnego i bezpiecznego kodu.
Problem:
Wielu programistów nie rozumie, jak dokładnie działają slice'y: slice — to nie sama tablica, ale struktura zawierająca wskaźnik na tablicę, długość i pojemność (capacity). Może to prowadzić do wycieków pamięci, błędów podczas pracy z kopiami oraz nieoczekiwanych efektów podczas zmiany oryginalnej tablicy.
Rozwiązanie:
Slice — to typ:
type slice struct { ptr unsafe.Pointer len int cap int }
Podczas rozszerzania slice'a za pomocą append() może dojść do ponownego przydzielenia tablicy podstawowej, a wszystkie wcześniejsze odniesienia do starej tablicy pozostaną ważne, ale będą wskazywać na stare dane. Nieznajomość tej cechy prowadzi do błędów i wycieków pamięci.
Przykład poprawnego przydzielania pamięci i kopiowania:
src := []int{1,2,3,4,5} dst := make([]int, len(src)) copy(dst, src)
Slice, utworzony za pomocą [:], dzieli tablicę podstawową, a ich modyfikacje wpływają na siebie nawzajem, jeśli nie zostanie wykonane copy.
Kluczowe cechy:
Co się stanie, gdy zwiększysz slice przez append, przekraczając cap, jeśli inne slice'y mają odniesienia do tej samej tablicy?
append przy przekroczeniu cap tworzy nową tablicę podstawową z nową lokalizacją w pamięci, i tylko ten slice odnosi się do nowej tablicy, podczas gdy pozostałe — do starej. To częsta przyczyna różnic danych.
Dlaczego ważne jest, aby nie przechowywać długo żyjących slice'ów małego rozmiaru, otrzymanych z dużej tablicy?
Nawet jeśli slice jest bardzo mały, jego wskaźnik przechowuje odniesienie do całej tablicy podstawowej, co może prowadzić do utrzymywania dużej tablicy w pamięci i wycieków pamięci.
Co się stanie, jeśli zslice'ujesz tablicę poza jej granicami?
Wystąpi panic: błąd uruchomienia: granice slice'a poza zakresem.
Funkcja odczytuje duży plik do tablicy bajtów i zwraca slice pierwszych 100 elementów. Ten slice jest potem długo przechowywany, ale cała pamięć pod dużą tablicą zostaje w GC.
Plusy:
Minusy:
Bezpośrednio po uzyskaniu slice'a następuje kopiowanie potrzebnego fragmentu do nowego slice'a za pomocą make i copy. Stara tablica jest natychmiast zapomniana, GC zwalnia pamięć.
Plusy:
Minusy: