Манипуляции со строками в Go: руководство по пакету strings

Строки — базовый тип во всех языках программирования. В Go строка — это неизменяемая последовательность байт, обычно закодированных в UTF‑8. Манипуляции со строками пригодятся в парсинге, валидации данных, формировании HTTP‑ответов и подготовке логов.
Краткая сводка о пакете strings
Пакет strings содержит функции для:
- поиска подстрок и символов;
- получения индексов (по байту и по руну);
- замены подстрок;
- разбиения и объединения;
- изменения регистра (верхний/нижний/заглавные);
- обрезки пробелов и специфичных символов;
- эффективной сборки строк через strings.Builder.
Важно: строки в Go неизменяемы. Любая «модификация» строкы возвращает новую строку. Для многократной конкатенации и снижения аллокаций применяйте strings.Builder или bytes.Buffer.
Поиск подстрок и символов
Определения:
- rune — кодовая точка Unicode (alias int32);
- byte — uint8, одна байтовая ячейка в строке;
Функции поиска:
- strings.Contains(s, substr) — содержит ли s подстроку substr;
- strings.ContainsAny(s, chars) — содержит ли s любой символ из chars;
- strings.ContainsRune(s, r) — содержит ли s руну r.
Пример:
import (
"fmt"
"strings"
)
func main() {
aString := "Hello, World!"
substring := "World"
characters := "aeiou"
aRune := 'o'
fmt.Println(strings.Contains(aString, substring)) // true
fmt.Println(strings.ContainsAny(aString, characters)) // true
fmt.Println(strings.ContainsRune(aString, aRune)) // true
}Поиск индекса
Функции Index* возвращают позицию (в байтах) или -1:
- strings.Index(s, substr)
- strings.IndexAny(s, chars)
- strings.IndexByte(s, b)
- strings.IndexRune(s, r)
- strings.IndexFunc(s, f) — индекс первой руны, для которой f возвращает true.
Пример с IndexFunc:
import (
"fmt"
"strings"
)
func main() {
aString := "Hello, world!"
substring := "world"
chars := "wrld"
byteCharacter := byte('o')
aRune := rune('o')
fmt.Println(strings.Index(aString, substring)) // 7
fmt.Println(strings.IndexAny(aString, chars)) // 7
fmt.Println(strings.IndexByte(aString, byteCharacter)) // 4
f := func(r rune) bool {
return r == 'o'
}
fmt.Println(strings.IndexFunc(aString, f)) // 4
fmt.Println(strings.IndexRune(aString, aRune)) // 4
}Важно: индексы возвращаются в байтах. Для работы с позициями в Unicode‑символах считайте руны с помощью for range или используйте package unicode/utf8.
Замена подстрок
Функции:
- strings.Replace(s, old, new, n) — заменить n вхождений (n<0 — все);
- strings.ReplaceAll(s, old, new) — заменить все вхождения.
Пример:
import (
"fmt"
"strings"
)
func main() {
theString := "This is a test string to be modified."
fmt.Println(strings.Replace(theString, "is", "was", 1)) // Replace first
fmt.Println(strings.Replace(theString, "is", "was", -1)) // Replace all
fmt.Println(strings.ReplaceAll(theString, "is", "was")) // Replace all
}Где стоит обратить внимание:
- Замена работает на байтовом уровне для строк; если ваш target содержит мультибайтовые руны, результат может отличаться от ожидаемого при использовании байтовых операций.
Разбиение и объединение строк
Функции для разбиения:
- strings.Split(s, sep)
- strings.SplitAfter(s, sep) — включает разделитель в результирующие элементы;
- strings.SplitN(s, sep, n)
- strings.SplitAfterN(s, sep, n)
Пример:
import (
"fmt"
"strings"
)
func main() {
s := "This is a test string to be split."
fmt.Println(strings.Split(s, " ")) // разделить по пробелу
fmt.Println(strings.SplitAfter(s, " ")) // разделитель остаётся
fmt.Println(strings.SplitAfterN(s, " ", 3))
fmt.Println(strings.SplitN(s, " ", 3))
}Объединение:
- strings.Join(elems, sep) — принимает срез строк и разделитель.
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Join([]string{"Hello", "World"}, ":"))
// Output: Hello:World
}Приведение регистра
Функции:
- strings.ToLower(s)
- strings.ToUpper(s)
- strings.ToTitle(s)
Пример:
import (
"fmt"
"strings"
)
func main() {
s := "This is a test string."
fmt.Println(strings.ToLower(s)) // this is a test string.
fmt.Println(strings.ToUpper(s)) // THIS IS A TEST STRING.
fmt.Println(strings.ToTitle(s)) // THIS IS A TEST STRING.
}Примечание: ToTitle делает заглавными руны; для корректной локализации (например, турецкий) используйте пакет golang.org/x/text/cases.
Эффективная сборка строк
Проблема: конкатенация с помощью + создаёт новые строки и может привести к множественным аллокациям.
Рекомендуемые решения:
- strings.Builder — специально для эффективной сборки строк;
- bytes.Buffer — удобен, когда работают байты и io.Writer интерфейсы.
Пример с strings.Builder:
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
b.WriteString("This ")
b.WriteString("is ")
b.WriteString("a ")
b.WriteString("test ")
b.WriteString("string.")
fmt.Println(b.Len())
str := b.String()
fmt.Println(str)
b.Reset()
b.WriteString("This ")
b.WriteString("is ")
b.WriteString("another ")
b.WriteString("test ")
b.WriteString("string.")
fmt.Println(b.Cap())
fmt.Println(b.String())
}Обрезка строк (trimming)
Функции:
- strings.Trim(s, cutset)
- strings.TrimLeft(s, cutset)
- strings.TrimRight(s, cutset)
- strings.TrimPrefix(s, prefix)
- strings.TrimSuffix(s, suffix)
- strings.TrimSpace(s)
Пример:
import (
"strings"
"fmt"
)
func main() {
s := " Hello, World! "
prefix := "Hello"
suffix := "World!"
fmt.Println(strings.Trim(s, " !")) // уберёт пробелы и восклицательные
fmt.Println(strings.TrimSpace(s)) // уберёт пробелы по краям
fmt.Println(strings.TrimLeft(s, " H")) // обрезает слева
fmt.Println(strings.TrimRight(s, "! ")) // обрезает справа
fmt.Println(strings.TrimPrefix(s, " ")) // убирает конкретный префикс
fmt.Println(strings.TrimSuffix(s, "! ")) // убирает конкретный суффикс
}Форматирование строк
Для форматирования строк и вывода используйте пакет fmt и его форматные спецификаторы: fmt.Sprintf, fmt.Fprintf и т. д. Это удобнее и безопаснее, чем ручная конкатенация.
Когда стандартный подход может не подойти
- Unicode и байты: многие функции работают в байтах; при работе с многобайтовыми рунами используйте for range, utf8 или unicode.
- Очень большие текстовые потоки: для потоковой обработки лучше применять bufio.Reader/Writer и читать по частям вместо загрузки всего текста в память.
- Сложные шаблоны совпадений: regexp может быть медленным; для простых требований разумнее использовать index/strings.Contains или библиотеку Aho‑Corasick для множественных шаблонов.
Важно: регулярные выражения удобны, но могут быть тяжёлыми по CPU и потреблять много памяти при неправильно составленных паттернах.
Альтернативные подходы и примеры
- regexp — для сложных совпадений и захвата групп;
- bytes package — работа на уровне []byte для оптимизации и совместимости с io;
- bufio.Scanner/Reader — для построчной работы с большими потоками;
- golang.org/x/text — расширенные операции с локализацией и преобразованием регистра.
Пример: безопасный подсчёт символов (рут) в строке:
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "こんにちは"
fmt.Println(utf8.RuneCountInString(s)) // количество рун
}Ментальные модели и эвристики
- Строка = неизменяемая последовательность байт в UTF‑8.
- Для операций с символами используйте руны; для операций с байтами — byte/slice.
- Для многократных конкатенаций используйте Builder/Buffer.
- Для поиска простых подстрок предпочитайте strings.Index / Contains; regexp — когда простых средств недостаточно.
Чек-листы по ролям
Для разработчика:
- Проверить, работают ли функции с Unicode корректно.
- Использовать strings.Builder при больших объёмах конкатенаций.
- Тестировать краевые случаи: пустые строки, строки с нулевыми байтами, только пробелы.
Для архитектора:
- Оценить объём данных и принять решение о потоковой или пакетной обработке.
- Выбрать между простыми строковыми операциями и индексированием/поисковыми структурами для производительности.
Для ревьювера кода:
- Ищите лишние аллокации (массовые конкатенации с +).
- Убедитесь, что для проверки символов используется rune, если требуется Unicode‑чувствительность.
Сниппеты — шпаргалка (cheat sheet)
- Проверка наличия: strings.Contains(s, “x”)
- Индекс: strings.Index(s, “x”)
- Замена всех: strings.ReplaceAll(s, “old”, “new”)
- Разбить: strings.Split(s, “,”)
- Объединить: strings.Join(vals, “,”)
- Привести к нижнему регистру: strings.ToLower(s)
- Обрезать пробелы: strings.TrimSpace(s)
- Собрать много строк: var b strings.Builder; b.WriteString(“x”)
Краткий глоссарий (1‑строчные определения)
- Строка: неизменяемая последовательность байт (UTF‑8).
- Rune: кодовая точка Unicode (int32).
- Byte: 8‑битный элемент строки.
- s.Builder / bytes.Buffer: структуры для эффективной сборки строк/байтов.
- Index vs RuneIndex: Index возвращает байтовую позицию.
Риски и рекомендации по безопасности
- Не доверяйте пользовательским данным при форматировании команд/шаблонов — избегайте прямого включения пользовательского ввода в форматные строки без экранирования.
- Для логов, содержащих пользовательские данные, применяйте маскирование чувствительных полей.
Краткое резюме
- Пакет strings покрывает большинство задач по работе со строками в Go.
- Для Unicode‑безопасности учитывайте различие между байтами и рунами.
- Для производительности при множественных конкатенациях используйте Builder или Buffer.
Важно: при миграции кода с других языков проверьте предположения о кодировке и изменяемости строк.
Спасибо за чтение — применяйте эти приёмы в повседневной работе с текстом в Go.