Работа со строками в Go: пакет strings

TL;DR
Пакет strings из стандартной библиотеки Go предоставляет набор функций для поиска, замены, разбиения, объединения, изменения регистра, обрезки и эффективной сборки строк. Для большой части задач достаточно этих функций, но для высокопроизводительных сценариев стоит учитывать кодировки, аллокции и альтернативы вроде буферов и rune-ориентированных функций.
Краткое описание
String — последовательность символов (байтов в Go-строке). Пакет strings работает в байтовой семантике для многих операций, но одновременно поддерживает Unicode через специфичные функции и работу с rune. Ниже — практическое руководство и советы по использованию основных возможностей пакета.
Варианты поисковых операций
Пакет предоставляет несколько полезных функций для поиска подстрок и символов: Contains, ContainsAny, ContainsRune, Index, IndexAny, IndexByte, IndexFunc, IndexRune.
Примеры кода (оригинальная форма сохраняется):
import "fmt"
import "strings"
func main() {
aString := "Hello, World!"
substring := "World"
characters := "aeiou"
aRune := 'o'
fmt.Println(strings.Contains(aString, substring)) // Output: true
fmt.Println(strings.ContainsAny(aString, characters)) // Output: true
fmt.Println(strings.ContainsRune(aString, aRune)) // Output: true
}Объяснения:
- strings.Contains возвращает true, если подстрока присутствует.
- strings.ContainsAny проверяет наличие любого из символов в наборе.
- strings.ContainsRune ищет конкретный rune (Unicode кодовую точку).
Index и похожие функции возвращают позицию первого вхождения или -1.
import (
"fmt"
"strings"
)
func main() {
aString := "Hello, world!"
substring := "world"
chars := "wrld"
byteCharacter := byte('o')
aRune := rune('o')
fmt.Println(strings.Index(aString, substring)) // Output: 7
fmt.Println(strings.IndexAny(aString, chars)) // Output: 7
fmt.Println(strings.IndexByte(aString, byteCharacter)) // Output: 4
f := func(r rune) bool {
return r == 'o'
}
fmt.Println(strings.IndexFunc(aString, f)) // Output: 4
fmt.Println(strings.IndexRune(aString, aRune)) // Output: 4
}Важно: IndexByte оперирует байтами, а IndexRune и IndexFunc ориентированы на rune/Unicode.
Замена подстрок
Для замены используйте Replace и ReplaceAll.
import (
"fmt"
"strings"
)
func main() {
theString := "This is a test string to be modified."
fmt.Println(strings.Replace(theString, "is", "was", 1))
fmt.Println(strings.Replace(theString, "is", "was", -1))
fmt.Println(strings.ReplaceAll(theString, "is", "was"))
}Примечание: Replace с третьим параметром n контролирует количество замен; -1 означает «все вхождения». ReplaceAll эквивалентен Replace с -1.
Разделение и объединение строк
Split, SplitAfter, SplitAfterN и SplitN возвращают срез строк, разделяя исходную строку.
- strings.Split(s, sep) — стандартное разделение по сепаратору.
- strings.SplitAfter(s, sep) — похожа, но включает сепаратор в результирующие элементы.
- SplitN и SplitAfterN ограничивают число кусков.
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:
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Join([]string{"Hello", "World"}, ":"))
// Output: "Hello:World"
}Изменение регистра
Простейшие функции:
- ToLower
- ToUpper
- ToTitle
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.
}Note: ToTitle выполняет простое преобразование в заглавные символы; для языков с сложными правилами заглавных букв может потребоваться дополнительные библиотеки.
Эффективная сборка строк
Для частых конкатенаций используйте strings.Builder (или bytes.Buffer в старых примерах). Builder уменьшает число аллокаций по сравнению с частым использованием оператора +.
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
// Write some strings to the builder
b.WriteString("This ")
b.WriteString("is ")
b.WriteString("a ")
b.WriteString("test ")
b.WriteString("string.")
// Get the length of the builder
fmt.Println(b.Len())
// Convert the builder to a string
str := b.String()
fmt.Println(str)
// Reset the builder
b.Reset()
// Write some more strings to the builder
b.WriteString("This ")
b.WriteString("is ")
b.WriteString("another ")
b.WriteString("test ")
b.WriteString("string.")
// Get the capacity of the builder
fmt.Println(b.Cap())
// Convert the builder to a string again
str = b.String()
fmt.Println(str)
}Советы по производительности:
- Предпочитайте strings.Builder при множественных конкатенациях в цикле.
- Если нужно собирать большие объёмы данных или работать с бинарными данными, bytes.Buffer может быть более уместен.
- Для форматирования с переменными используйте fmt.Sprintf экономно — он аллоцирует строку.
Обрезка строк
Функции: Trim, TrimLeft, TrimRight, TrimPrefix, TrimSuffix, TrimSpace.
import (
"strings"
"fmt"
)
func main() {
// the full string
s := "Hello, World!"
// the element for the trim
prefix := "Hello"
suffix := "World!"
// trims a string by specified trim set
fmt.Println(strings.Trim(s, "!"))
// trims out by spaces in the beginning and end of the string
fmt.Println(strings.TrimSpace(s))
// trims from the left string by specified trim set
fmt.Println(strings.TrimLeft(s, "Hello"))
// trims out from the right string by specified trim set
fmt.Println(strings.TrimRight(s, "World!"))
// trims out a prefix
fmt.Println(strings.TrimPrefix(s, prefix))
// trims out a specific suffix
fmt.Println(strings.TrimSuffix(s, suffix))
}Форматирование строк
Для форматирования используйте пакет fmt с C-подобными спецификаторами (%%s, %%d и т.д.). fmt.Sprintf возвращает отформатированную строку.
Пример:
import (
"fmt"
)
func main() {
name := "Gopher"
version := 1
s := fmt.Sprintf("%s v%d", name, version)
fmt.Println(s)
}Когда стандартные функции не подойдут
- При работе с очень большими потоками текста в tight loop может потребоваться профилирование и оптимизация аллокаций.
- Для сложной нормализации Unicode (например, NFC/NFD) стандартный пакет strings не предоставляет средств; используйте golang.org/x/text/unicode/norm.
- Если нужна потоковая обработка без удержания всей строки в памяти, рассматривайте io.Reader и буферные операции.
Альтернативные подходы
- bytes.Buffer — удобен, когда вы работаете с байтами.
- fmt.Fprintf на bytes.Buffer — удобно для комбинирования форматирования и сборки.
- regexp — для сложных шаблонных замен (но дорог по производительности).
- golang.org/x/text — для работы с локализацией и расширенной поддержкой Unicode.
Ментальные модели и эвристики
- Строка в Go — неизменяемый срез байтов (internal: последовательность байтов). Для частых модификаций используйте Builder.
- Всегда учитывайте, работаете ли вы с байтами или rune: индекс в байтах != индекс в символах для UTF-8.
- Предпочитайте специализированные функции (Trim, ReplaceAll, SplitN) — они компактны и оптимизированы.
Чек-лист для практики
- Проверить, используются ли функциональные возможности пакета strings перед написанием собственной реализации.
- Выбрать strings.Builder для множественных конкатенаций.
- Использовать IndexFunc/ContainsRune при работе с Unicode.
- Не применять regexp без необходимости (профайлить сначала).
- Для нормализации Unicode использовать x/text.
Тестовые случаи и критерии приёмки
- Поиск: проверка Index/Contains для ASCII и UTF-8 строк.
- Замена: Replace с n=1, n=-1 и ReplaceAll дают ожидаемый результат.
- Split/Join: split+join возвращает исходную строку (с учётом сепараторов).
- Builder: многократные WriteString и проверка результата и Len/Cap.
Частые ошибки и как их избежать
- Ошибка: использование IndexByte для Unicode-символов. Правильно: IndexRune или IndexFunc.
- Ошибка: ожидание, что len(s) возвращает число «символов»; он возвращает байты. Для символов используйте utf8.RuneCountInString.
- Ошибка: преждевременная оптимизация без профайлинга — сначала убедитесь, что это узкое место.
Мини‑глоссарий (1 строка)
- Rune: кодовая точка Unicode; Byte: один байт в UTF-8 представлении; Builder: оптимизированный для сборки строк тип.
Итог
Пакет strings покрывает большинство повседневных задач по работе со строками в Go: поиск, замена, разбиение, объединение, изменение регистра, обрезка и эффективная сборка. Для сложной работы с Unicode или требовательной к производительности логики стоит сочетать strings с bytes, fmt, regexp и пакетами из golang.org/x.
Важно: всегда профилируйте реальные кейсы, прежде чем оптимизировать.
Похожие материалы
Загрузить Raspberry Pi с SSD — полное руководство
Синхронизация звука в VLC на Android TV
Фото в картину: эффект текстуры в Photoshop
Воспроизвести MKV на iPhone и iPad — VLC и способы
Как очистить кэш на смарт‑телевизоре