← Назад к вопросам
Как устроена строка в Go?
2.2 Middle🔥 201 комментариев
#Основы Go#Производительность и оптимизация
Комментарии (1)
🐱
gpt-5-nanoPrepBro AI4 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как устроена строка в Go
Go-проекторы часто спрашивают про строку как тип данных и как она реализована в памяти. Ниже — сжатый, но подробный разбор с примерами и практиками.
Что такое строка в Go
- В Go строка — это тип данных, который представляет собой последовательность байтов. По умолчанию она считается закодированной в формате UTF-8, и в этом смысле строка должна содержать корректно кодированные символы Unicode.
- Строки в Go неизменяемы (immutable). Любая операция, которая «изменяет» строку, на самом деле создает новую строку.
- Внутренний смысл: строка — это не просто набор символов, а ссылка на последовательность байтов и её длина. Это позволяет эффективно делать срезы и копировать ссылки, но не копировать данные.
Как устроена строка в памяти
- В реализации Go строка хранит две части: указатель на данные и длину. Это похоже на заголовок «многоячеечного» массива, но без способности изменять размер.
- В 64-битных архитектурах строка обычно занимает две машинных слова: pointer и len. Это как бы легковесный заголовок над непрерывной областью байтов.
- Спецификация языка не диктует точную реализацию памяти; детали зависят от реализации рантайма. Но повседневное поведение таково: строка — это ссылочная структура, данные которой могут быть общими между суб-строками, если не копировать.
Байты, руны и UTF-8
- len(s) возвращает количество байтов, а не количество символов.
- s[i] возвращает один байт (тип uint8). Поэтому напрямую доступ к символам может работать неправильно для многобайтовых кодовых точек.
- Чтобы пройтись по символам Unicode (рунам), используйте range или пакет utf8:
- range по строке итерирует по рунам.
Примеры
- Объявление и обычная конкатенация:
package main
import "fmt"
func main() {
s := "Привет, мир" // UTF-8 строка
fmt.Println(s)
}
- Доступ по байту и срезы:
package main
import "fmt"
func main() {
s := "Привет"
fmt.Printf("len(s) = %d\n", len(s)) // байты
fmt.Printf("s[0] = %d ('%c')\n", s[0], s[0]) // первый байт
sub := s[1:3] // подстрока без копирования данных
fmt.Println(sub)
}
- Итерация по рунам:
package main
import "fmt"
func main() {
s := "Привет, 世界"
for i, r := range s {
fmt.Printf("%d: %c (%U)\n", i, r, r)
}
}
- Преобразования и копирование:
package main
import "fmt"
func main() {
s := "Hello"
b := []byte(s) // копия байтов
s2 := string(b) // создаёт новую строку из тех же байтов (копия данных)
fmt.Println(s, b, s2)
}
Raw-строки и экранирование
- Raw строка с помощью обратных кавычек сохраняет переносы строк и не требует экранирования:
raw := `Line1
Line2\tNotEscaped`
- Двойные кавычки поддерживают escapes:
quoted := "Line1\nLine2\tTabbed"
Производительность и практики
- Избегайте многократной конкатенации с помощью оператора + в цикле для больших строк — это приводит к созданию новых строк на каждой итерации.
- Для сборки больших строк используйте:
- strings.Builder
- bytes.Buffer (для байтовых данных)
- Помните про эффект «задержки памяти» при срезах: срез строки s[a:b] может удерживать память всего исходного массива, если он велик. Если нужен небольшой фрагмент без удержания большого массива, стоит скопировать подстроку в новую строку:
frag := s[a:b]
copy := string([]byte(frag)) // копирование в новую строку
Важные детали
- zero-value строки: "".
- строки и байты — не одно и то же, Conversion между ними копирует данные.
- При работе с Unicode рекомендуется опираться на руны и пакет utf8 для корректной обработки границ символьных единиц.
Итого: строка в Go — это immutable непрерывный массив байтов с длиной, где байты кодируют данные в UTF-8; работа с символами требует учета кодировки, а эффективная сборка строк — через Builder и разумное копирование при необходимости.