Строки и форматирование
Строки в Go -- это неизменяемые (immutable) последовательности байтов, обычно содержащие текст в UTF-8. Go предоставляет богатый набор пакетов для работы со строками: strings, strconv, fmt, regexp, bytes и unicode.
Пакет strings
Поиск и проверка
import "strings"
s := "Hello, World!"
// Contains -- содержит ли подстроку
strings.Contains(s, "World") // true
strings.Contains(s, "world") // false (case-sensitive)
strings.ContainsAny(s, "aeiou") // true (any of these chars)
strings.ContainsRune(s, 'W') // true
// HasPrefix / HasSuffix
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "!") // true
// Index -- позиция первого вхождения (-1 если не найдено)
strings.Index(s, "World") // 7
strings.LastIndex(s, "l") // 10
// Count
strings.Count(s, "l") // 3
strings.Count("cheese", "e") // 3
Разделение и соединение
// Split
parts := strings.Split("a,b,c,d", ",")
// ["a", "b", "c", "d"]
// SplitN -- максимум N частей
parts = strings.SplitN("a,b,c,d", ",", 2)
// ["a", "b,c,d"]
// SplitAfter -- сохраняет разделитель
parts = strings.SplitAfter("a,b,c", ",")
// ["a,", "b,", "c"]
// Fields -- разделение по пробелам (любое кол-во)
words := strings.Fields(" hello world go ")
// ["hello", "world", "go"]
// FieldsFunc -- разделение по кастомному условию
words = strings.FieldsFunc("foo1bar2baz", func(r rune) bool {
return r >= '0' && r <= '9'
})
// ["foo", "bar", "baz"]
// Join
result := strings.Join([]string{"Go", "is", "awesome"}, " ")
// "Go is awesome"
Замена и трансформация
// Replace
s := strings.Replace("foo bar foo", "foo", "baz", 1) // "baz bar foo"
s = strings.ReplaceAll("foo bar foo", "foo", "baz") // "baz bar baz"
// Trim
s = strings.TrimSpace(" hello ") // "hello"
s = strings.Trim("***hello***", "*") // "hello"
s = strings.TrimLeft("***hello", "*") // "hello"
s = strings.TrimRight("hello***", "*") // "hello"
s = strings.TrimPrefix("hello.go", "hello") // ".go"
s = strings.TrimSuffix("hello.go", ".go") // "hello"
// Case conversion
s = strings.ToUpper("hello") // "HELLO"
s = strings.ToLower("HELLO") // "hello"
s = strings.Title("hello world") // Deprecated in 1.18+
// Use golang.org/x/text/cases instead
// Repeat
s = strings.Repeat("ha", 3) // "hahaha"
// Map -- apply function to each rune
s = strings.Map(func(r rune) rune {
if r == 'o' {
return '0'
}
return r
}, "foo bar")
// "f00 bar"
EqualFold -- регистро-независимое сравнение
strings.EqualFold("Go", "go") // true
strings.EqualFold("Go", "GO") // true
strings.EqualFold("straße", "STRASSE") // true (Unicode-aware!)
strings.Builder -- эффективная конкатенация
strings.Builder минимизирует аллокации при построении строк:
func buildQuery(fields []string, table string, conditions []string) string {
var b strings.Builder
// Grow pre-allocates buffer (optional but efficient)
b.Grow(256)
b.WriteString("SELECT ")
for i, f := range fields {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(f)
}
b.WriteString(" FROM ")
b.WriteString(table)
if len(conditions) > 0 {
b.WriteString(" WHERE ")
for i, c := range conditions {
if i > 0 {
b.WriteString(" AND ")
}
b.WriteString(c)
}
}
return b.String()
}
Никогда не конкатенируйте строки в цикле через
+. Это O(n^2) из-за создания новой строки каждый раз.strings.Builder-- O(n).
strings.NewReader -- строка как io.Reader
r := strings.NewReader("Hello, World!")
// Now you can use it wherever io.Reader is expected
data, _ := io.ReadAll(r) // Read all
json.NewDecoder(strings.NewReader(s)).Decode(&v) // JSON decode
http.Post(url, "text/plain", strings.NewReader(body)) // HTTP body
Пакет strconv -- конвертация типов
import "strconv"
// String <-> Int
n, err := strconv.Atoi("42") // string -> int: 42, nil
s := strconv.Itoa(42) // int -> string: "42"
// ParseInt with base and bit size
n64, err := strconv.ParseInt("FF", 16, 64) // hex: 255
n64, err = strconv.ParseInt("1010", 2, 64) // binary: 10
n64, err = strconv.ParseInt("-42", 10, 32) // decimal int32: -42
// ParseFloat
f, err := strconv.ParseFloat("3.14", 64) // 3.14 as float64
f, err = strconv.ParseFloat("1e10", 64) // 10000000000
// ParseBool
b, err := strconv.ParseBool("true") // true
b, err = strconv.ParseBool("1") // true
b, err = strconv.ParseBool("false") // false
b, err = strconv.ParseBool("0") // false
// FormatInt
s = strconv.FormatInt(255, 16) // "ff"
s = strconv.FormatInt(10, 2) // "1010"
s = strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
s = strconv.FormatFloat(3.14, 'e', 2, 64) // "3.14e+00"
s = strconv.FormatFloat(3.14, 'g', -1, 64) // "3.14"
// Quote / Unquote -- для Go string литералов
s = strconv.Quote("Hello\nWorld") // `"Hello\nWorld"`
s, err = strconv.Unquote(`"Hello\n"`) // "Hello\n"
// AppendInt -- append to []byte without allocation
buf := make([]byte, 0, 20)
buf = strconv.AppendInt(buf, 42, 10) // []byte("42")
Пакет fmt -- форматирование
Printf и форматирующие глаголы (verbs)
type User struct {
Name string
Age int
}
u := User{"Alice", 30}
// General
fmt.Printf("%v\n", u) // {Alice 30} -- default format
fmt.Printf("%+v\n", u) // {Name:Alice Age:30} -- with field names
fmt.Printf("%#v\n", u) // main.User{Name:"Alice", Age:30} -- Go syntax
fmt.Printf("%T\n", u) // main.User -- type
// Integers
fmt.Printf("%d\n", 42) // 42 -- decimal
fmt.Printf("%b\n", 42) // 101010 -- binary
fmt.Printf("%o\n", 42) // 52 -- octal
fmt.Printf("%O\n", 42) // 0o52 -- octal with prefix
fmt.Printf("%x\n", 42) // 2a -- hex lowercase
fmt.Printf("%X\n", 42) // 2A -- hex uppercase
fmt.Printf("%08d\n", 42) // 00000042 -- zero-padded
fmt.Printf("%+d\n", 42) // +42 -- always show sign
// Strings
fmt.Printf("%s\n", "hello") // hello -- plain string
fmt.Printf("%q\n", "hello") // "hello" -- quoted
fmt.Printf("%10s\n", "hi") // hi -- right-aligned
fmt.Printf("%-10s\n", "hi") // hi -- left-aligned
// Floats
fmt.Printf("%f\n", 3.14) // 3.140000 -- decimal
fmt.Printf("%.2f\n", 3.14) // 3.14 -- 2 decimal places
fmt.Printf("%e\n", 3.14) // 3.140000e+00 -- scientific
fmt.Printf("%g\n", 3.14) // 3.14 -- compact
// Pointers
fmt.Printf("%p\n", &u) // 0xc0000b4000
// Width and precision
fmt.Printf("%10d\n", 42) // 42 -- width 10
fmt.Printf("%-10d|\n", 42) // 42 | -- left-aligned
fmt.Printf("%010d\n", 42) // 0000000042 -- zero-padded
// Error formatting with %w (fmt.Errorf only)
err := fmt.Errorf("query failed: %w", sql.ErrNoRows)
// errors.Is(err, sql.ErrNoRows) == true
Sprintf, Fprintf, Errorf
// Sprintf -- format to string
msg := fmt.Sprintf("User %s has %d points", name, points)
// Fprintf -- format to io.Writer
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintf(w, "Hello, %s!", name) // w is http.ResponseWriter
// Errorf -- format error with wrapping
err := fmt.Errorf("parsing config %s: %w", filename, err)
fmt.Stringer и fmt.GoStringer
type Color struct {
R, G, B uint8
}
// String -- controls %v and %s output
func (c Color) String() string {
return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
}
// GoString -- controls %#v output (Go syntax)
func (c Color) GoString() string {
return fmt.Sprintf("Color{R: %d, G: %d, B: %d}", c.R, c.G, c.B)
}
c := Color{255, 128, 0}
fmt.Println(c) // #ff8000
fmt.Printf("%v\n", c) // #ff8000
fmt.Printf("%#v\n", c) // Color{R: 255, G: 128, B: 0}
Пакеты unicode и unicode/utf8
import (
"unicode"
"unicode/utf8"
)
// unicode -- character classification
unicode.IsLetter('A') // true
unicode.IsDigit('5') // true
unicode.IsSpace(' ') // true
unicode.IsUpper('A') // true
unicode.IsLower('a') // true
unicode.IsPunct('!') // true
unicode.ToUpper('a') // 'A'
unicode.ToLower('A') // 'a'
// utf8 -- UTF-8 encoding operations
s := "Привет, мир!"
utf8.RuneCountInString(s) // 12 (rune count, not byte count!)
len(s) // 22 (byte count)
utf8.ValidString(s) // true
utf8.Valid([]byte(s)) // true
// Decode first rune
r, size := utf8.DecodeRuneInString(s)
fmt.Printf("%c, %d bytes\n", r, size) // П, 2 bytes
// Encode rune to bytes
buf := make([]byte, 4)
n := utf8.EncodeRune(buf, 'Я')
fmt.Println(buf[:n]) // [208 175]
Помните:
len(s)возвращает количество байтов, не символов. Для подсчёта символов используйтеutf8.RuneCountInString(s)илиfor _, r := range s.
Пакет regexp -- регулярные выражения
import "regexp"
// Compile -- returns error if pattern is invalid
re, err := regexp.Compile(`\d+`)
if err != nil {
log.Fatal(err)
}
// MustCompile -- panics on error (use for constants)
re = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
// Basic matching
re.MatchString("[email protected]") // true
// Find
re2 := regexp.MustCompile(`\d+`)
re2.FindString("abc 123 def 456") // "123" (first match)
re2.FindAllString("abc 123 def 456", -1) // ["123", "456"] (all matches)
re2.FindAllString("abc 123 def 456", 1) // ["123"] (first N matches)
// Find with submatch (groups)
re3 := regexp.MustCompile(`(\w+)@(\w+)\.(\w+)`)
match := re3.FindStringSubmatch("[email protected]")
// ["[email protected]", "alice", "example", "com"]
// Named groups
re4 := regexp.MustCompile(`(?P<user>\w+)@(?P<domain>\w+\.\w+)`)
match = re4.FindStringSubmatch("[email protected]")
names := re4.SubexpNames()
for i, name := range names {
if i > 0 && name != "" {
fmt.Printf("%s: %s\n", name, match[i])
}
}
// user: alice
// domain: example.com
// Replace
result := re2.ReplaceAllString("price: 100 USD, tax: 20 USD", "XXX")
// "price: XXX USD, tax: XXX USD"
// Replace with function
result = re2.ReplaceAllStringFunc("price: 100, tax: 20", func(s string) string {
n, _ := strconv.Atoi(s)
return strconv.Itoa(n * 2)
})
// "price: 200, tax: 40"
// Split
parts := regexp.MustCompile(`[,;:\s]+`).Split("a, b; c: d e", -1)
// ["a", "b", "c", "d", "e"]
Важно: Регулярные выражения Go используют синтаксис RE2 (без backtracking). Это гарантирует линейное время выполнения, но не поддерживает lookahead/lookbehind.
Предкомпиляция для производительности
// BAD: compiles regex on every call
func isEmail(s string) bool {
matched, _ := regexp.MatchString(`^[\w.]+@[\w.]+\.\w+$`, s)
return matched
}
// GOOD: compile once, reuse
var emailRegex = regexp.MustCompile(`^[\w.]+@[\w.]+\.\w+$`)
func isEmail(s string) bool {
return emailRegex.MatchString(s)
}
Пакет bytes -- зеркало strings для []byte
Пакет bytes предоставляет те же функции, что strings, но для []byte:
import "bytes"
data := []byte("Hello, World!")
bytes.Contains(data, []byte("World")) // true
bytes.HasPrefix(data, []byte("Hello")) // true
bytes.Split(data, []byte(",")) // [[]byte("Hello"), []byte(" World!")]
bytes.Replace(data, []byte("World"), []byte("Go"), 1) // "Hello, Go!"
bytes.ToUpper(data) // "HELLO, WORLD!"
bytes.TrimSpace([]byte(" hello ")) // "hello"
bytes.Equal([]byte("a"), []byte("a")) // true
// bytes.Buffer -- mutable byte buffer
var buf bytes.Buffer
buf.WriteString("Hello")
buf.WriteByte(',')
buf.WriteString(" World!")
fmt.Println(buf.String()) // "Hello, World!"
// bytes.NewReader -- []byte as io.Reader
r := bytes.NewReader(data)
Когда использовать bytes вместо strings:
- Работа с бинарными данными
- Избежание конвертации
string<->[]byte - Работа с I/O (всё в Go -- байты)