Каков самый простой / наилучший / корректный способ перебора символов строки в Java?

StackOverflow https://stackoverflow.com/questions/196830

Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

Два варианта

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

или

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

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

Обратите внимание, что большинство других методов, описанных здесь, ломаются, если вы имеете дело с символами за пределами BMP (Unicode Базовая многоязычная плоскость ), то есть кодовые точки , которые находятся за пределами 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

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

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

Результат на моем Powerbook с частотой 2,6 ГГц (это Mac :-)) и JDK 1.5:

  • Тест 1:Символ + Строка --> 3138 мсек
  • Тест 2:Строка, преобразованная в массив -> 9568 мсек
  • Тест 3:Параметр StringBuilder -> 3536 мсек
  • Тест 4:Символитератор и строка -> 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");

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

import java.text.*;

final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
   // process c
   ...
}

Если у вас есть гуава в вашем пути к классам, приведенная ниже довольно удобочитаемая альтернатива , В 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

В 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 no   дольше имеет непосредственное сопоставление с основной семантической единицей в   Unicode. JDK 5 был обновлен для поддержки большего набора символов   ценности. Вместо изменения определения типа char , некоторые из   новые дополнительные символы представлены суррогатной парой   из двух значений char . Чтобы уменьшить путаницу имен, кодовая точка будет   используется для обозначения числа, которое представляет конкретный Unicode   характер, в том числе дополнительные.

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

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

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

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

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

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

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

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

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

Javadoc говорит:

  

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

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

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

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

Видишь Учебные пособия по Java:Струны.

public class StringDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
        int len = palindrome.length();
        char[] tempCharArray = new char[len];
        char[] charArray = new char[len];

        // put original string in an array of chars
        for (int i = 0; i < len; i++) {
            tempCharArray[i] = palindrome.charAt(i);
        } 

        // reverse array of chars
        for (int j = 0; j < len; j++) {
            charArray[j] = tempCharArray[len - 1 - j];
        }

        String reversePalindrome =  new String(charArray);
        System.out.println(reversePalindrome);
    }
}

Поместите длину в int len и использовать for петля.

StringTokenizer совершенно не подходит для задачи разбиения строки на отдельные символы. С String # split () вы можете легко это сделать, используя регулярное выражение, которое ничего не соответствует, например:

String[] theChars = str.split("|");

Но StringTokenizer не использует регулярные выражения, и вы не можете указать строку-разделитель, которая будет соответствовать ничему между символами. is один симпатичный маленький хак, который вы можете использовать для достижения той же цели: использовать саму строку как строку разделителя (делая каждый символ в ней разделителем) и заставить ее вернуть разделители:

StringTokenizer st = new StringTokenizer(str, str, true);

Однако я упоминаю эти варианты только с целью их отклонения. Оба метода разбивают исходную строку на односимвольные строки вместо символьных примитивов, и оба требуют больших накладных расходов в форме создания объекта и манипуляции со строками. Сравните это с вызовом charAt () в цикле for, который практически не требует дополнительных затрат.

Разработка этого ответа и этот ответ .

Приведенные выше ответы указывают на проблему многих решений, которые здесь не повторяются по значению кодовой точки - у них могут возникнуть проблемы с любым суррогатные символы . В документации по Java также описана проблема здесь . (см. «Представления символов Unicode»). В любом случае, вот некоторый код, который использует некоторые фактические суррогатные символы из дополнительного набора Unicode и преобразует их обратно в строку. Обратите внимание, что .toChars () возвращает массив символов: если вы имеете дело с суррогатами, у вас обязательно будет два символа. Этот код должен работать для любого символа Юникода.

    String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
    supplementary.codePoints().forEach(cp -> 
            System.out.print(new String(Character.toChars(cp))));

Этот пример кода поможет вам!

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Solution {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("a", 10);
        map.put("b", 30);
        map.put("c", 50);
        map.put("d", 40);
        map.put("e", 20);
        System.out.println(map);

        Map sortedMap = sortByValue(map);
        System.out.println(sortedMap);
    }

    public static Map sortByValue(Map unsortedMap) {
        Map sortedMap = new TreeMap(new ValueComparator(unsortedMap));
        sortedMap.putAll(unsortedMap);
        return sortedMap;
    }

}

class ValueComparator implements Comparator {
    Map map;

    public ValueComparator(Map map) {
        this.map = map;
    }

    public int compare(Object keyA, Object keyB) {
        Comparable valueA = (Comparable) map.get(keyA);
        Comparable valueB = (Comparable) map.get(keyB);
        return valueB.compareTo(valueA);
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top