Как правильно измерить длину строки в Java: от length() до графем

Строки в Java кажутся простыми, из коробки поддерживается UTF-16, но стоит вам добавить эмодзи, и привычный length() выдаёт «магические» числа. В этой статье разберём, как на самом деле устроены строки в Java, почему эмодзи занимают два char, и как корректно подсчитать количество символов, видимых глазом.

Проблема: length() возвращает не то, что вы ждёте

Возьмём классический пример:

Мы ожидаем увидеть 1, но вывод — 2. Всё дело во внутреннем представлении строк: Java хранит текст в кодировке UTF-16, где каждый символ кодируется одним или двумя 16-битными значениями типа char.

  • Базовые символы Unicode (U+0000 … U+FFFF) умещаются в один char.
  • Символы с кодом > U+FFFF (например, почти все эмодзи) кодируются суррогатной парой – двумя значениями char (старший и младший суррогат).

Метод String.length() возвращает количество char-единиц, а не количество логических символов (code points). Для эмодзи 😊 (U+1F60A) это действительно 2.

Решение в первом приближении: codePointCount()

Чтобы получить количество Unicode code points в строке, используйте метод:

Метод codePointCount(int beginIndex, int endIndex) подсчитывает code points в заданном диапазоне, правильно обрабатывая суррогатные пары.

Альтернатива через стримы (Java 8+):

codePointCount() работает быстро и подходит для 99% задач, где нужно считать именно код-пойнты (числовые значения символов). Но не всё так просто.

Но и это не идеально: графемы

Что если строка содержит составной символ, например:

  • Флаг: "🇺🇳" (флаг ООН) – состоит из двух региональных индикаторов: 🇺 и 🇳.
  • Семья: "👨‍👩‍👦" – здесь несколько code points (мужчина, женщина, мальчик и соединители).
  • Буквы с диакритикой: "é" – буква e + комбинирующий знак ударения (U+0301).

Метод codePointCount() вернёт для "👨‍👩‍👦" значение 5, хотя визуально мы видим один символ. Такие визуально неделимые единицы называются графемами (grapheme clusters).

В стандарте Unicode графема – это пользовательский символ, который отображается как единый знак.

Класс java.text.BreakIterator умеет разбивать текст на границы графем, слов, предложений и т.д. Используем его для подсчёта видимых символов:

Если ваше приложение интенсивно работает с эмодзи, флагами или текстами на языках с диакритикой (например, вьетнамский), используйте BreakIterator для подсчёта видимых символов.

 

Комментарии к «Как правильно измерить длину строки в Java: от length() до графем»

Понравилась статья? Есть вопросы? - пишите в комментариях.



Комментарий: