Подстроки за O(1)
Сергей заказал автокомплит имён по первым буквам. Уже на тестовых данных поле выдачи показывало мусор: обрезок имени «привет» разрывал букву пополам. Фичу вернули на доработку.
подстрока не копирует байты и делается мгновенно, но держит весь исходный массив живым и режет строго по границам байтов.
У строки есть второе ключевое свойство — неизменяемость. Строку нельзя изменить после создания:
s := "hello"
s[0] = 'H' // cannot assign to s[0] — ошибка компиляции
Неизменяемость — не ограничение ради чистоты, а то, что делает строки безопасными для свободного расшаривания. Когда несколько мест в программе держат одну и ту же строку, никто из них не может испортить данные остальным.
Срез — новый заголовок, не копия
У неизменяемости есть приятное следствие: взятие подстроки не копирует байты. path[5:] просто создаёт новый заголовок, указывающий в тот же массив.
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Подстрока делается за O(1) независимо от длины исходной строки. Можно резать гигабайтный текст на куски, и каждый кусок будет дёшевым.
Цена дешёвого среза
Плата за это две. Первая — про память: исходный массив не может быть собран сборщиком мусора, пока жива хотя бы одна подстрока, указывающая в него.
big := readHugeFile() // строка на гигабайт
head := big[:10] // отрезали 10 байт
// big не освободится, пока жив head — вся гигабайтная строка живёт дальше
Вторая плата — про границы. Резать можно только по границам байтов. Границы символов Go за вас не проверяет, и срез, попавший в середину многобайтового символа, даст битую строку — без паники, просто с мусором внутри:
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Вы узнаете:
- как «отвязать» срез от большого исходника копированием
- как работать с байтовыми индексами в горячем коде
Проверьте себя
Валидация «Полки» срезает имя по байтовому индексу: s := "привет"; fmt.Println(s[1:4]). Что выведет программа?
Что происходит при взятии подстроки name := path[5:]?
Почему гигабайтная строка big не освобождается, пока жив её маленький срез head := big[:10]?
правильных ответов
Срез — это новый заголовок на тот же массив: O(1), без копирования. Цена: исходный массив держится в памяти, пока жив хотя бы один срез, а границы символов никто не проверяет.
10 000 ₽ · 60 минут · онлайн