Как я могу выполнять итерацию по кодовым точкам Unicode строки Java?
Итак, я знаю о String#codePointAt(int)
, но он индексируется смещением char
, а не смещением кодовой точки.
Я думаю о том, чтобы попробовать что-то вроде:
Но мои проблемы -
- Я не уверен, будут ли кодовые точки, которые естественно находятся в диапазоне высоких суррогатов, будут сохранены как два значения
char
или один
- это похоже на ужасный дорогой способ итерации через символы
- кто-то должен придумать что-то лучшее.
Ответы
Ответ 1
Да, Java использует кодировку UTF-16-esque для внутренних представлений строк, и, да, она кодирует символы вне базовой многоязычной плоскости (BMP), используя схему суррогатного материнства.
Если вы знаете, что будете иметь дело с символами вне BMP, то вот канонический способ перебора символов строки Java:
final int length = s.length();
for (int offset = 0; offset < length; ) {
final int codepoint = s.codePointAt(offset);
// do something with the codepoint
offset += Character.charCount(codepoint);
}
Ответ 2
Java 8 добавила CharSequence#codePoints
, которая возвращает IntStream
, содержащую коды.
Вы можете напрямую использовать поток для перебора по ним:
string.codePoints().forEach(c -> ...);
или с циклом for, собирая поток в массив:
for(int c : string.codePoints().toArray()){
...
}
Эти пути, вероятно, дороже, чем решение Джонатана Фейнберга, но они быстрее читают/пишут, а разница в производительности обычно незначительна.
Ответ 3
Итерация над кодовыми точками подается как запрос функции на Sun.
См. Запись с ошибкой Sun
Также есть пример того, как итерации по String CodePoints есть.
Ответ 4
Думаю, я бы добавил метод обходного пути, который работает с циклами foreach (ref), плюс вы можете преобразовать его в java 8 new String # codePoints легко, когда вы переходите на java 8:
public static Iterable<Integer> codePoints(final String string) {
return new Iterable<Integer>() {
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
int nextIndex = 0;
public boolean hasNext() {
return nextIndex < string.length();
}
public Integer next() {
int result = string.codePointAt(nextIndex);
nextIndex += Character.charCount(result);
return result;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
Затем вы можете использовать его с foreach следующим образом:
for(int codePoint : codePoints(myString)) {
....
}
Или поочередно, если вы просто хотите преобразовать строку в массив int (который может использовать больше ОЗУ, чем описанный выше подход):
public static List<Integer> stringToCodePoints(String in) {
if( in == null)
throw new NullPointerException("got null");
List<Integer> out = new ArrayList<Integer>();
final int length = in.length();
for (int offset = 0; offset < length; ) {
final int codepoint = in.codePointAt(offset);
out.add(codepoint);
offset += Character.charCount(codepoint);
}
return out;
}