Какой самый простой / лучший / самый правильный способ перебирать символы строки в Java? - PullRequest
274 голосов
/ 13 октября 2008

StringTokenizer? Преобразовать String в char[] и повторить это? Что-то еще?

Ответы [ 14 ]

297 голосов
/ 13 октября 2008

Я использую цикл for для итерации строки и использую charAt(), чтобы каждый символ проверял ее. Поскольку String реализован в виде массива, метод charAt() является операцией с постоянным временем.

String s = "...stuff...";

for (int i = 0; i < s.length(); i++){
    char c = s.charAt(i);        
    //Process char
}

Я бы так и сделал. Это кажется мне самым простым.

Что касается правильности, я не верю, что она существует здесь. Все это основано на вашем личном стиле.

178 голосов
/ 13 октября 2008

Два варианта

for(int i = 0, n = s.length() ; i < n ; i++) { 
    char c = s.charAt(i); 
}

или

for(char c : s.toCharArray()) {
    // process c
}

Первое, вероятно, быстрее, а второе, вероятно, более читабельно.

88 голосов
/ 12 декабря 2008

Обратите внимание, что большинство других методов, описанных здесь, ломаются, если вы имеете дело с символами вне BMP (Unicode Basic Multilingual Plane ), то есть кодовые точки , которые находятся за диапазон u0000-uFFFF. Это случается редко, так как кодовые точки вне этого в основном назначаются мертвым языкам. Но помимо этого есть некоторые полезные символы, например, некоторые кодовые точки, используемые для математической записи, а некоторые используются для кодирования собственных имен на китайском языке.

В этом случае ваш код будет:

String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
  int curChar = str.codePointAt(offset);
  offset += Character.charCount(curChar);
  // do something with curChar
}

Для метода Character.charCount(int) требуется Java 5 +.

Источник: http://mindprod.com/jgloss/codepoint.html

24 голосов
/ 12 декабря 2008

Я согласен, что StringTokenizer здесь перебор. На самом деле я опробовал предложения выше и взял время.

Мой тест был довольно прост: создать StringBuilder с примерно миллионом символов, преобразовать его в строку и перебрать каждый из них с помощью charAt () / после преобразования в массив char / с CharacterIterator тысячу раз (конечно сделать что-то для строки, чтобы компилятор не мог оптимизировать весь цикл :-)).

Результат на моем Powerbook 2.6 ГГц (это mac :-)) и JDK 1.5:

  • Тест 1: charAt + String -> 3138 мсек
  • Тест 2: строка преобразуется в массив -> 9568 мсек
  • Тест 3: символ StringBuilder -> 3536 мсек
  • Тест 4: CharacterIterator и String -> 12151 мсек

Поскольку результаты значительно отличаются, самый простой способ также кажется самым быстрым. Интересно, что charAt () в StringBuilder кажется немного медленнее, чем в String.

Кстати, я предлагаю не использовать CharacterIterator, так как считаю его злоупотребление символом '\ uFFFF' "концом итерации" действительно ужасным хаком. В больших проектах всегда есть два парня, которые используют один и тот же вид взлома для двух разных целей, и код действительно таинственно падает.

Вот один из тестов:

    int count = 1000;
    ...

    System.out.println("Test 1: charAt + String");
    long t = System.currentTimeMillis();
    int sum=0;
    for (int i=0; i<count; i++) {
        int len = str.length();
        for (int j=0; j<len; j++) {
            if (str.charAt(j) == 'b')
                sum = sum + 1;
        }
    }
    t = System.currentTimeMillis()-t;
    System.out.println("result: "+ sum + " after " + t + "msec");
19 голосов
/ 13 октября 2008

Для этого есть несколько специальных классов:

import java.text.*;

final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
   // process c
   ...
}
17 голосов
/ 08 марта 2011

Если у вас есть Гуава на вашем пути к классам, следующее является довольно удобочитаемой альтернативой. У Guava даже есть довольно разумная реализация List для этого случая, так что это не должно быть неэффективно.

for(char c : Lists.charactersOf(yourString)) {
    // Do whatever you want     
}

ОБНОВЛЕНИЕ: Как отметил @Alex, в Java 8 также есть CharSequence#chars для использования. Даже типом является IntStream, поэтому он может отображаться на символы типа:

yourString.chars()
        .mapToObj(c -> Character.valueOf((char) c))
        .forEach(c -> System.out.println(c)); // Or whatever you want
12 голосов
/ 06 января 2015

Если вам нужно перебрать кодовые точки String (см. Этот ответ ), то более короткий / более читаемый способ - использовать метод CharSequence#codePoints, добавленный в Java 8:

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

или использование потока вместо цикла for:

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

Существует также CharSequence#chars, если вы хотите поток символов (хотя это IntStream, поскольку CharStream нет).

11 голосов
/ 10 декабря 2017

В Java 8 мы можем решить это как:

String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));

Метод chars () возвращает IntStream, как указано в doc :

Возвращает поток значений int, расширяющих ноль, из этого последовательность. Любой символ, который отображается на суррогатную кодовую точку, передается через не истолковано. Если последовательность видоизменяется во время потока читаемый результат не определен.

Метод codePoints() также возвращает IntStream согласно документу:

Возвращает поток значений кодовой точки из этой последовательности. любой суррогатные пары, встречающиеся в последовательности, объединяются, как если бы Character.toCodePoint и результат передается в поток. любой другие единицы кода, включая обычные символы BMP, непарные суррогаты и неопределенные единицы кода расширяются от нуля до значений int которые затем передаются в поток.

Чем отличаются символ и кодовая точка? Как упоминалось в этой статье:

Unicode 3.1 добавлены дополнительные символы, в результате чего общее количество символов до более чем 216 символов, которые могут быть отличается от одного 16-битного char. Следовательно, значение char дольше имеет непосредственное сопоставление с основной семантической единицей в Unicode. JDK 5 был обновлен для поддержки большего набора символов ценности. Вместо изменения определения типа char, некоторые из новые дополнительные символы представлены суррогатной парой из двух char значений. Чтобы уменьшить путаницу имен, кодовая точка будет используется для обозначения числа, которое представляет конкретный Unicode персонаж, в том числе дополнительные.

Наконец, почему forEachOrdered, а не forEach?

Поведение forEach явно недетерминировано, когда forEachOrdered выполняет действие для каждого элемента этого потока, в порядке встречи потока , если поток имеет определенный порядок встречи. Таким образом, forEach не гарантирует, что заказ будет сохранен. Также проверьте этот вопрос для получения дополнительной информации.

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

3 голосов
/ 13 октября 2008

Я бы не использовал StringTokenizer, так как это один из классов в JDK, который унаследован.

Джавадок говорит:

StringTokenizer это унаследованный класс, который сохраняется по соображениям совместимости хотя его использование не рекомендуется в новых код. Рекомендуется, чтобы кто-нибудь ища эту функциональность использовать метод разделения String или java.util.regex пакет вместо.

0 голосов
/ 24 декабря 2018

Если вам нужна производительность, тогда вы должны протестировать в своей среде. Другого пути нет.

Вот пример кода:

int tmp = 0;
String s = new String(new byte[64*1024]);
{
    long st = System.nanoTime();
    for(int i = 0, n = s.length(); i < n; i++) {
        tmp += s.charAt(i);
    }
    st = System.nanoTime() - st;
    System.out.println("1 " + st);
}

{
    long st = System.nanoTime();
    char[] ch = s.toCharArray();
    for(int i = 0, n = ch.length; i < n; i++) {
        tmp += ch[i];
    }
    st = System.nanoTime() - st;
    System.out.println("2 " + st);
}
{
    long st = System.nanoTime();
    for(char c : s.toCharArray()) {
        tmp += c;
    }
    st = System.nanoTime() - st;
    System.out.println("3 " + st);
}
System.out.println("" + tmp);

Вкл. Java онлайн Я получаю:

1 10349420
2 526130
3 484200
0

На Android x86 API 17 я получаю:

1 9122107
2 13486911
3 12700778
0
...