Skip to content

Data Structures Deep Dive

Data Structures

Slice, map и string — структуры, которые Go-разработчик использует сотни раз в день. Но сколько из них могут объяснить, почему append иногда мутирует оригинал, а иногда нет? Почему нельзя взять &m[key]? Или почему len("Привет") возвращает 12, а не 6?

Профайлер регулярно показывает неожиданные аллокации на безобидном append, slice aliasing bugs проскальзывают в production, а разница между подходами к конкатенации строк может составлять 10x по производительности. Эти "базовые" структуры — одна из главных слепых зон Senior разработчиков.

Обзор структур данных

🗺️
Data Structures Overview
Интерактивная карта структур данных Go
Открыть

TL;DR: Memory Layout

СтруктураHeaderBacking StoragePass-byMutableCopy Trigger
slice24 bytesHeap (backing array)Value (header)Elements: Yesappend beyond cap
string16 bytesHeap or rodataValueNo (immutable)Any modification
map8 bytesHeap (hmap+buckets)PointerYesNever (reference)
arrayN × sizeof(T)Stack or HeapValue (full copy)YesAlways on assign

Ключевой инсайт

Slice и string — value types с pointer внутри: при передаче в функцию header копируется, но backing storage остаётся общим. Map — это pointer type: переменная m уже содержит указатель на runtime.hmap.

Memory Layout Overview

┌─────────────────────────────────────────────────────────────────────────────────┐
│                         Data Structures Memory Layout                           │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                 │
│   SLICE (24 bytes header)              STRING (16 bytes header)                 │
│   ═══════════════════════              ════════════════════════                 │
│                                                                                 │
│   ┌─────────────────┐                  ┌─────────────────┐                      │
│   │ ptr ─────────┐  │                  │ ptr ─────────┐  │                      │
│   │ len = 3      │  │                  │ len = 12     │  │  (bytes, not runes)  │
│   │ cap = 8      │  │                  └──────────────┼──┘                      │
│   └──────────────┼──┘                                 │                         │
│                  │                                    ▼                         │
│                  ▼                     ┌─────────────────────┐                  │
│   ┌──────────────────────┐             │ "Привет" (UTF-8)    │ (immutable)      │
│   │ [0] [1] [2] ... [7]  │ (mutable)   └─────────────────────┘                  │
│   └──────────────────────┘                                                      │
│              Heap                                 Heap / rodata                 │
│                                                                                 │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                 │
│   MAP (8 bytes pointer)                                                         │
│   ═════════════════════                                                         │
│                                                                                 │
│   ┌─────────┐      ┌─────────────────────────────────────────────────────┐      │
│   │ *hmap ──┼─────▶│ hmap                                                │      │
│   └─────────┘      │ ┌────────────────────────────────────────────────┐  │      │
│                    │ │ count, B, hash0, buckets, oldbuckets, nevacuate│  │      │
│                    │ └────────────────────────────────────────────────┘  │      │
│                    │           │                                         │      │
│                    │           ▼                                         │      │
│                    │ ┌─────────────────────────────────────────────────┐ │      │
│                    │ │ buckets: [bucket0][bucket1]...[bucket_2^B]      │ │      │
│                    │ │          ├─tophash[8]─┤                         │ │      │
│                    │ │          ├─keys[8]────┤                         │ │      │
│                    │ │          ├─values[8]──┤                         │ │      │
│                    │ │          └─overflow───┘                         │ │      │
│                    │ └─────────────────────────────────────────────────┘ │      │
│                    └─────────────────────────────────────────────────────┘      │
│                                          Heap                                   │
│                                                                                 │
└─────────────────────────────────────────────────────────────────────────────────┘

Структура раздела

Slice Internals

24-байтовый header (ptr, len, cap), backing array на heap, growth strategy (изменённая в Go 1.18). Escape analysis для размещения: когда slice целиком на stack. Внутренности runtime.growslice, расчёт новой capacity.

Slice Append

Семантика append: когда создаётся новый backing array, а когда мутируется существующий. Главная ловушка — shared array при cap > len. Решения: slices.Clone(), three-index slice s[low:high:max], явное копирование.

Map Internals

Структура hmap, bmap (bucket), overflow chains, evacuation при load factor ~6.5. Почему нельзя взять &m[key] — нет стабильных адресов. Iteration randomization как защита от hash-DoS. Специализированные map_fast*.go для частых типов ключей.

String & Rune

Immutable UTF-8 bytes под капотом. Индексация s[i] возвращает byte, не rune. range автоматически декодирует UTF-8. strings.Builder для эффективной конкатенации. Конвертации []bytestring и когда они zero-copy.

Связь с Runtime

Операции над структурами данных тесно связаны с memory allocator:

  • make([]T, n)runtime.makeslicemallocgc
  • make(map[K]V, hint)runtime.makemap → bucket allocation
  • String literals → rodata section (не heap)

См. Stack vs Heap для понимания escape analysis и когда backing array размещается на stack.

Дальнейшее чтение

Go Deep Dive — книга для Senior разработчиков