9. strconv. Конвертация строк и чисел
strconv: Мост между бинарным миром и текстом
«В первом издании книги „Язык программирования Си“ мы уместили функцию atoi в три строчки кода. Она была простой, изящной и делала ровно то, что требовалось. Пакет
strconvв Go бережно хранит этот дух минимализма, добавляя строгость требований современного оборудования и управления памятью».
— Брайан Керниган
В компьютерных системах существует два параллельных мира: мир процессора и памяти, где живут компактные бинарные числа (int, float, uint64), и мир внешних протоколов, где царствует текст (HTTP-заголовки, JSON-пейлоады, конфигурационные файлы YAML и SQL-строки).
Пакет strconv (string conversion) — это канонический высокоскоростной мост между этими мирами. В отличие от универсального fmt.Sprintf, который тратит драгоценные такты процессора на парсинг форматирующей строки во время выполнения и вынужден боксить аргументы в interface{} (heap allocations), strconv предоставляет строго типизированные, специализированные функции. Они компилируются в прямолинейный, векторизованный машинный код.
Для бэкенд-инженера умение быстро и без лишних аллокаций преобразовывать идентификаторы, порты, суммы и счетчики — фундаментальный навык оптимизации горячих путей сервиса.
1. Конвертация целых чисел: Atoi, Itoa и ParseInt
strconv.Atoi vs strconv.ParseInt
Функция Atoi (ASCII to integer) — это классическая Bell Labs утилита, представляющая собой удобную обертку над ParseInt(s, 10, 0). Она возвращает платформо-зависимый тип int (размер которого равен 32 или 64 битам в зависимости от целевой архитектуры CPU).
// Atoi — быстрый путь для типичного int
n, err := strconv.Atoi("12345")
// ParseInt — когда нужна строгая битность или другая система счисления
// Возвращает int64. Последний аргумент (0) означает "использовать размер int"
n64, err := strconv.ParseInt("12345", 10, 64)
Когда использовать что:
Atoi: Когда вы работаете со стандартнымиint(ID сущностей, счетчики, номера страниц пагинации) и вам важна максимальная лаконичность кода.ParseInt: Когда нужно строго контролировать размерность (например, парсингint8илиint64для бинарных протоколов и полей базы данных) или основание системы счисления (например, парсинг HEX-цветов или хэшейbase=16).
strconv.Itoa и FormatInt
Обратная операция — преобразование целого числа в текстовую строку. Функция Itoa (integer to ASCII) — это просто прозрачная обертка над вызовом FormatInt(int64(i), 10).
2. Работа с плавающей точкой: ParseFloat и FormatFloat
Конвертация чисел с плавающей точкой (float64) на порядок сложнее из-за специфики аппаратного стандарта IEEE 754.
f, err := strconv.ParseFloat("3.14159", 64)
s := strconv.FormatFloat(3.14159, 'f', 2, 64) // "3.14"
Форматы вывода (fmt byte)
'f': Десятичная запись (-ddd.dddd). Стандарт для денежных величин и простых физических величин.'e'/'E': Научная нотация экспоненты (-d.dddde±dd).'g'/'G'**: Автоматический выбор: использует'e', если экспонента меньше -4 или больше/равна точности, иначе выбирает'f'`. Убирает хвостовые нули. Идеально для общего назначения.'b': Двоичная экспонента (для отладки низкоуровневой точности мантиссы).
3. Mechanical Sympathy: Избегаем аллокаций с Append
Самая мощная, но часто упускаемая из виду часть пакета strconv — функции семейства Append*.
Стандартные функции Itoa и FormatFloat неизбежно выделяют память под новую строку в куче. Функции Append (такие как strconv.AppendInt, strconv.AppendFloat, strconv.AppendBool) принимают существующий срез []byte и дописывают текстовый результат прямо в него, используя уже доступную емкость (cap).
flowchart TD
subgraph AllocPattern["Стандартный Itoa (Аллокация в куче)"]
direction TB
A1["strconv.Itoa(id)"] --> H1["Выделение памяти в куче (heap alloc)"]
H1 --> S1["Возврат новой string (16B заголовок + данные)"]
end
subgraph ZeroAllocPattern["AppendInt (Zero-Alloc в существующий буфер)"]
direction TB
B1["buf := bufferPool.Get()"] --> A2["strconv.AppendInt(buf, id, 10)"]
A2 --> B2["Запись символов прямо в память среза"]
B2 --> B3["0 аллокаций в куче!"]
end
classDef redBox fill:#7f1d1d,stroke:#f87171,color:#fee2e2;
classDef greenBox fill:#064e3b,stroke:#10b981,color:#d1fae5;
class H1,S1 redBox;
class A2,B2,B3 greenBox;
// ❌ Плохо: Аллокация новой строки в куче на каждом шаге
idStr := strconv.Itoa(userID)
key := "user:" + idStr
// ✅ Хорошо: Нулевая аллокация (если буфер достаточной емкости)
var buf []byte
buf = append(buf, "user:"...)
buf = strconv.AppendInt(buf, int64(userID), 10)
// buf теперь содержит "user:123"
key := string(buf) // Единственная аллокация в конце, если нужно именно string
Это критически важно в горячих путях бэкенда:
- Генерация составных ключей для Redis/Memcached.
- Формирование структурированных логов в формате JSON вручную.
- Сборка SQL-запросов и метрик для Prometheus/StatsD.
Использование семейства Append позволяет переиспользовать один и тот же буфер (например, из пула sync.Pool или структуры bytes.Buffer), сводя давление на сборщик мусора (GC) к абсолютному нулю.
4. Булевы значения и кватернионы
Функция strconv.ParseBool спроектирована чрезвычайно практично: она принимает не только строгие литералы "true"/"false", но и "1"/"0", "t"/"f", "T"/"F", "yes"/"no" и т.д. Это незаменимо при парсинге переменных окружения или параметров запроса, где пользователи могут писать что угодно:
// Принимает: 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False
val, err := strconv.ParseBool(os.Getenv("DEBUG"))
5. Сравнение подходов: strconv vs fmt vs cast
| Метод | Скорость | Аллокации | Гибкость | Использование |
|---|---|---|---|---|
strconv.Itoa/ParseInt |
🚀 Очень быстро | 1 (результат) | Низкая (только base 10) | Основная работа с числами |
strconv.AppendInt |
🚀🚀 Максимально быстро | 0 (в существующий буфер) | Низкая | Высоконагруженные системы, генерация ключей |
fmt.Sprintf("%d") |
🐢 Медленно | 2+ (интерфейсы + строка) | Высокая (форматирование) | Логи, человеко-читаемый вывод |
github.com/spf13/cast |
🐇 Средне | Зависит от контекста | Очень высокая (any -> type) | Конфигурации, маппинг структур |
6. Ловушки и нюансы
Локаль и разделители дробной части
Пакет strconv всегда использует строгую точку . как разделитель дробной части числа, независимо от региональных настроек (локали) операционной системы. Это строго соответствует стандарту RFC 4627 (JSON) и подавляющему большинству сетевых протоколов. Если вы парсите ввод от пользователя, который может ввести запятую (например, 3,14 в русской или немецкой локали), ParseFloat вернет ошибку. В таких сценариях необходимо предварительно заменять запятые на точки через strings.Replace.
Точность float32 vs float64
Язык Go по умолчанию предпочитает float64. Функция ParseFloat всегда возвращает float64. Если в вашей структуре данных используется поле типа float32, вы должны явно привести тип: float32(f). При этом возможна потеря точности округления. Напротив, FormatFloat позволяет явно указать битность (32 или 64), чтобы округление соответствовало исходному представлению.
f64, _ := strconv.ParseFloat("0.1", 64)
f32 := float32(f64)
// f32 может не точно представлять 0.1 из-за ограничений IEEE 754 для 32 бит
Итог
- Используйте
strconvвместоfmtдля конвертации чисел в продакшен-коде. Это на порядок быстрее и не нагружает Garbage Collector мусором. Appendфункции — ваши лучшие друзья для высоконагруженных приложений. Они позволяют дописывать байты в существующие срезы без единой аллокации.- Всегда проверяйте ошибки. Парсинг внешних данных (HTTP, БД, конфиги) никогда не бывает гарантированно успешным.
- Помните про локаль.
strconvработает только с точкой как разделителем десятичных дробей. Atoi— это синтаксический сахар дляParseInt(s, 10, 0). Используйте его для удобства, но понимайте, что он возвращает платформо-зависимыйint.
Освоив конвертацию примитивов, мы подходим к фундаментальной теме: работе с текстом, выходящим за рамки латинского ASCII. Как Go на самом низком уровне обрабатывает кириллицу, эмодзи и сложные многобайтовые кодировки? В следующей статье: 10. unicode и unicode/utf8. Работа с рунами и UTF-8.