Как получить количество символов (рун) в строке Go, а не байтов?
Почему len() считает байты, а не символы?
В Go строки — это неизменяемые последовательности байтов. Функция len() возвращает количество байтов, а не символов. Русские буквы, эмодзи и другие символы Unicode занимают от 1 до 4 байт в кодировке UTF-8. Например, буква "п" занимает 2 байта, поэтому строка "пароль" из 6 символов весит 12 байт.
Правильное решение: преобразование в руны
В Go тип rune представляет один символ Unicode (32-битное целое число). Чтобы получить количество символов, нужно преобразовать строку в срез рун с помощью []rune():
package main
import "fmt"
func main() {
password := "пароль"
charCount := len([]rune(password))
fmt.Printf("Количество символов: %d\n", charCount) // Вывод: 6
// Проверка длины пароля
if charCount < 8 {
fmt.Println("Пароль слишком короткий")
} else {
fmt.Println("Пароль подходит")
}
}
Альтернативный подход: пакет utf8
Можно использовать функцию utf8.RuneCountInString() из стандартного пакета unicode/utf8. Этот способ эффективнее, так как не создаёт новый срез:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
password := "пароль"
charCount := utf8.RuneCountInString(password)
fmt.Printf("Количество символов: %d\n", charCount) // Вывод: 6
if charCount < 8 {
fmt.Println("Пароль слишком короткий")
}
}
Работа с эмодзи и сложными символами
Важно: []rune() и utf8.RuneCountInString() считают кодовые точки Unicode. Некоторые эмодзи (например, семейные пары, флаги стран) состоят из нескольких кодовых точек, объединённых с помощью модификаторов. Для них эти методы могут дать некорректный результат:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
text := "👨👩👧👦" // Эмодзи "семья"
fmt.Println("Байт:", len(text)) // 25
fmt.Println("Рун:", utf8.RuneCountInString(text)) // 7 (каждый человек + модификаторы)
}
Для подсчёта графем (видимых символов) используйте пакет golang.org/x/text:
package main
import (
"fmt"
"golang.org/x/text/unicode/norm"
)
func main() {
text := "👨👩👧👦"
// Нормализация и подсчёт графем
iter := norm.Iter{}
iter.InitString(norm.NFC, text)
count := 0
for !iter.Done() {
iter.Next()
count++
}
fmt.Println("Графем:", count) // 1 (один видимый символ)
}
Рекомендации
- Для большинства случаев (английские буквы, кириллица, простые эмодзи) используйте
utf8.RuneCountInString()— он быстрее и не выделяет память. - Если нужна высокая производительность и вы уверены, что строка содержит только ASCII, можно использовать
len(). - Для работы с текстом на естественных языках (с учётом диакритических знаков, сцепок символов) используйте пакет
golang.org/x/textили библиотекуrivo/uniseg.
В вашем случае с паролем из русских букв решение с []rune() или utf8.RuneCountInString() будет работать корректно.
Использование пакета unicode/utf8 для итерации по символам
Если вам нужно не только посчитать символы, но и обработать каждый из них (например, проверить, что пароль содержит хотя бы одну цифру), можно использовать итератор из пакета unicode/utf8:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
password := "пароль123"
count := 0
for i := 0; i < len(password); {
r, size := utf8.DecodeRuneInString(password[i:])
fmt.Printf("Символ: %c (код: %U)\n", r, r)
count++
i += size
}
fmt.Println("Всего символов:", count)
}
Этот подход позволяет одновременно подсчитать символы и получить доступ к каждому руну без создания промежуточного среза. Функция utf8.DecodeRuneInString() возвращает руну и количество байтов, которое она занимает. Это особенно полезно при работе с большими строками, когда создание среза рун может быть затратным по памяти.
Также обратите внимание на функцию utf8.RuneCount(), которая работает с срезом байтов, а не строкой — она пригодится, если у вас уже есть []byte.
Важно: все методы на основе рун корректно работают с кириллицей, иероглифами и большинством эмодзи, но не учитывают комбинированные символы (например, буква "ё" как "е" + диакритический знак). Для полной поддержки Unicode используйте нормализацию или библиотеку uniseg.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Оптимизация производительности Kotlin приложений: советы и примеры