Як я можу повторити через кодові точки unicode Java-рядка?


105

Тож я знаю про String#codePointAt(int), але він індексується charзміщенням, а не зміщенням кодової точки.

Я думаю про спробувати щось на кшталт:

  • використовуючи String#charAt(int)для отримання charіндексу
  • тестування, чи charзнаходиться в діапазоні високих сурогатів
    • якщо так, використовуйте String#codePointAt(int)для отримання кодової точки та збільшення індексу на 2
    • якщо ні, використовуйте задане charзначення як кодову точку та збільшуйте індекс на 1

Але мої побоювання є

  • Я не впевнений, чи кодові точки, які, природно, в діапазоні сурогатів, зберігатимуться як два charзначення або одне
  • це здається жахливим дорогим способом перегляду символів
  • хтось, мабуть, придумав щось краще.

Відповіді:


143

Так, Java використовує кодування UTF-16 esque для внутрішнього представлення Strings, і, так, воно кодує символи поза базовою багатомовною площиною ( 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. Але якщо ви маєте справу лише з латинськими / європейськими / кириличними / грецькими / івритськими / арабськими сценаріями, то ви просто s.charAt () до змісту вашого серця. :)
Джонатан Файнберг

24
Але ти не повинен. Наприклад, якщо ваша програма виводить XML і якщо хтось дає їй якийсь незрозумілий математичний оператор, раптом ваш XML може бути недійсним.
Механічний равлик

2
Я б використав offset = s.offsetByCodePoints(offset, 1);. Чи є якась користь у використанні offset += Character.charCount(codepoint);замість цього?
Пол Гроке

3
@Mechanicalsnail Я не розумію ваш коментар. Чому виведення XML спричинить неправильну поведінку цієї відповіді?
Гілі

3
@Gili відповідь чудова. Він посилався на коментар @Jonathan Feinberg, в якому він виступає за використання charAt()поганої ідеї
RecursiveExceptionException

72

Додана Java 8, CharSequence#codePointsяка повертає IntStreamмістять кодові точки. Ви можете використовувати потік безпосередньо для перегляду над ними:

string.codePoints().forEach(c -> ...);

або з циклом for для збору потоку в масив:

for(int c : string.codePoints().toArray()){
    ...
}

Ці способи, ймовірно, дорожчі, ніж рішення Джонатана Фейнбергса , але вони швидше читати / писати, і різниця у виконанні зазвичай буде незначною.


3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())також працює.
saka1029

2
Трохи коротша версія коду @ saka1029: s:for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
Лій


7

Думав, що я додам обхідний метод, який працює з циклами foreach ( ref ), плюс ви можете перетворити його в новий рядок # codePoints java 8 метод для Java 8, коли ви переходите на java 8:

Ви можете використовувати його з foreach так:

 for(int codePoint : codePoints(myString)) {
   ....
 }

Ось помічник mthod:

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();
        }
      };
    }
  };
}

Або по черзі, якщо ви просто хочете перетворити рядок в масив 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;
  }

Вдячно використовує "codePoints" безпечно обробляє сурогатну пару UTF-16 (внутрішнє представлення рядків Java).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.