Как правильно вычислить длину строки в Java? - PullRequest
18 голосов
/ 26 июля 2011

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

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

Ответы [ 5 ]

23 голосов
/ 26 июля 2011

Нормальная модель длины строки 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 - Это описание было на некоторых версиях вопроса. Смотрите историю редактирования ... если у вас достаточно точек повторения.

11 голосов
/ 27 июля 2011

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

В большинстве случаев это должно сработать.

4 голосов
/ 24 декабря 2016

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

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

Чтобы дать вам представление о том, как каждый из различных методов может возвращать разные длины для одних и тех же данных, я создал этот класс , чтобы быстро сгенерировать длины текста Unicode, содержащегося в thisстраница , которая предназначена для всестороннего тестирования различных языков с неанглийскими символами.Вот результаты выполнения этого кода после нормализации входного файла тремя различными способами (без нормализации, 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.

0 голосов
/ 26 июля 2011

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

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

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

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

0 голосов
/ 26 июля 2011

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

(Как отметил Стивен С в комментариях, Единицы кода Unicode == Символы Java)

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

...