Как правильно вычислить длину строки в Java?

Я знаю, что есть String#length и различные методы в Character, которые более или менее работают с кодовыми единицами/кодовыми точками.

Каков предложенный способ в Java, чтобы фактически вернуть результат в соответствии со стандартами Unicode (UAX # 29), принимая такие вещи, как язык/язык, нормализация и графема кластеров?

Ответы

Ответ 1

java.text.BreakIterator способен перебирать текст и может сообщать о символах "символ", слово, предложение и границы.

Рассмотрим этот код:

def length(text: String, locale: java.util.Locale = java.util.Locale.ENGLISH) = {
  val charIterator = java.text.BreakIterator.getCharacterInstance(locale)
  charIterator.setText(text)

  var result = 0
  while(charIterator.next() != BreakIterator.DONE) result += 1
  result
}

Запуск:

scala> val text = "Thîs lóo̰ks we̐ird!"
text: java.lang.String = Thîs lóo̰ks we̐ird!

scala> val length = length(text)
length: Int = 17

scala> val codepoints = text.codePointCount(0, text.length)
codepoints: Int = 21 

С суррогатными парами:

scala> val parens = "\uDBFF\uDFFCsurpi\u0301se!\uDBFF\uDFFD"
parens: java.lang.String = 􏿼surpíse!􏿽

scala> val length = length(parens)
length: Int = 10

scala> val codepoints = parens.codePointCount(0, parens.length)
codepoints: Int = 11

scala> val codeunits = parens.length
codeunits: Int = 13

Это должно выполнять работу в большинстве случаев.

Ответ 2

Нормальная модель длины строки Java

String.length() указывается как возвращающее число значений char ( "единиц кода" ) в строке. Это наиболее общее определение длины строки Java; см. ниже.

Ваше описание 1 семантики length на основе размера массива основы/массива неверно. Тот факт, что значение, возвращаемое length(), также является размером массива поддержки или среза массива, является лишь деталью реализации типичных библиотек классов Java. String не нужно выполнять таким образом. В самом деле, я думаю, что видел реализации Java String, где он НЕ реализован таким образом.


Альтернативные модели длины строки.

Чтобы получить количество кодовых точек Unicode в строке, используйте str.codePointCount(0, str.length()) - см. javadoc.

Чтобы получить размер (в байтах) строки в другой кодировке, используйте str.getBytes(charset).length.

Для решения проблем, связанных с локальностью, вы можете использовать Normalizer для нормализации строки в любой форме, наиболее подходящей для вашего использования, и затем используйте codePointCount, как указано выше.

Но в некоторых случаях даже это не сработает; например правила учета в Венгрии, которые, по-видимому, не соответствуют стандарту Unicode.


Использование String.length() обычно в порядке

Причина, по которой большинство приложений использует String.length(), заключается в том, что большинство приложений не связаны с подсчетом количества символов в словах, текстах и ​​т.д. с точки зрения человека. Например, если я это сделаю:

String s = "hi mum how are you";
int pos = s.indexOf("mum");
String textAfterMum = s.substring(pos + "mum".length());

действительно не имеет значения, что "mum".length() не возвращает кодовые точки или что это не лингвистически правильный счетчик символов. Он измеряет длину строки с использованием модели, соответствующей задаче. И это работает.

Очевидно, что при использовании многоязычного текстового анализа все становится немного сложнее; например поиск слов. Но даже тогда, если вы нормализуете свой текст и параметры перед запуском, вы можете безопасно кодировать в терминах "единицы кода", а не "кодовые точки" большую часть времени; т.е. length() все еще работает.


1 - Это описание было в некоторых версиях вопроса. См. Историю изменений... если у вас есть достаточные точки ответа.

Ответ 3

Это зависит от того, что вы подразумеваете под "длиной [the] String":

  • String.length() возвращает число chars в String. Обычно это полезно только для программирования связанных задач, таких как выделение буферов, потому что многобайтовая кодировка может вызвать проблемы, что означает, что char не означает one Кодовая точка Unicode.
  • String.codePointCount(int, int) и Character.codePointCount(CharSequence,int,int) оба возвращают количество кодовых точек Unicode в String. Обычно это полезно только для программирования связанных задач, требующих просмотра String в виде последовательности кодов Unicode без необходимости беспокоиться о мешающем многобайтовом кодировании.
  • BreakIterator.getCharacterInstance(Locale) можно использовать для получения следующего grapheme в String для данного Locale. Используя это несколько раз, вы можете подсчитать количество графемов в String. Поскольку графемы - это в основном буквы (в большинстве случаев), этот метод полезен для получения количества записываемых символов, которые содержит String. По сути этот метод возвращает примерно тот же номер, который вы получите, если вручную подсчитать количество букв в String, что делает его полезным для таких вещей, как определение пользовательских интерфейсов и разделение Strings без искажения данных.

Чтобы дать вам представление о том, как каждый из разных методов может возвращать разные длины для одних и тех же данных, я создал этот класс для быстро создайте длины текста Юникода, содержащиеся в этой странице, которая предназначена для всестороннего тестирования многих разных языков с неанглийскими символами, Ниже приведены результаты выполнения этого кода после нормализации входного файла тремя способами (без нормализации, NFC, NFD):

Input UTF-8 String
>>  String.length() = 3431
>>  String.codePointCount(int,int) = 3431
>>  BreakIterator.getCharacterInstance(Locale) = 3386
NFC Normalized UTF-8 String
>>  String.length() = 3431
>>  String.codePointCount(int,int) = 3431
>>  BreakIterator.getCharacterInstance(Locale) = 3386
NFD Normalized UTF-8 String
>>  String.length() = 3554
>>  String.codePointCount(int,int) = 3554
>>  BreakIterator.getCharacterInstance(Locale) = 3386

Как вы можете видеть, даже "одинаковый" String может давать разные результаты для длины, если вы используете либо String.length(), либо String.codePointCount(int,int).

Для получения дополнительной информации по этой теме и другим подобным темам вы должны прочитать это сообщение в блоге, которое охватывает различные основы использования Java для правильной дескриптор Unicode.

Ответ 4

String.length() не возвращает размер массива, поддерживающего строку, а фактическую длину строки, определяемую как "количество единиц кода Unicode в строке". (см. API docs).

(Как отметил Стивен С в комментариях, Unicode code units == Java chars)

Если это не то, что вы ищете, то, возможно, вы должны уточнить вопрос немного.

Ответ 5

Если вы имеете в виду подсчет длины строки в соответствии с грамматическими правилами языка, тогда ответ не будет, нет такого алгоритма в Java и нигде.

Нет, если алгоритм также не выполняет полный семантический анализ текста.

В венгерском, например, sz и zs может считаться одной буквой или двумя, что зависит от состава слова, в котором они появляются. (Например: ország - 5 букв, тогда как torzság равно 7.)

Uodate. Если все, что вам нужно, это стандартное количество символов Unicode (что, как я уже указывал, неточно), преобразование вашей строки в форму NFKC с помощью java.text.Normalizer может быть решением.