Каков самый простой / лучший / самый правильный способ перебора символов строки в Java?
StringTokenizer
? Преобразовать String
в char[]
и перебрать ее? Что-то другое?
- Как проверить, отображается ли один символ в строке?
- Индексы всех вхождений символа в строку
- Какие символы мне нужно скрывать в документах XML?
- Как изменить диакритические символы на недиакритические
- Загрузить пакет R из символьной строки
- Удалить все вхождения символа из строки
- Как читать ввод по-символам в Java?
- преобразовать формат столбца data.frame из символа в фактор
Я использую цикл 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+.
Я согласен с тем, что StringTokenizer здесь переполнен. На самом деле я опробовал приведенные выше предложения и нашел время.
Мой тест был довольно прост: создайте StringBuilder с примерно миллионом символов, преобразуйте его в String и перейдите по каждому из них с помощью charAt () / после преобразования в массив символов / с CharacterIterator тысячу раз (конечно, убедившись, что сделайте что-нибудь в строке, чтобы компилятор не смог оптимизировать весь цикл :-)).
Результат на моем 2,6 ГГц Powerbook (это mac :-)) и JDK 1.5:
- Тест 1: charAt + String -> 3138msec
- Тест 2: строка, преобразованная в массив -> 9568 мсек
- Тест 3: StringBuilder charAt -> 3536мс
- Тест 4: CharacterIterator и String -> 12151msec
Поскольку результаты существенно различаются, наиболее простой способ, по-видимому, является самым быстрым. Интересно, что 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
Для этого есть несколько выделенных classов:
import java.text.*; final CharacterIterator it = new StringCharacterIterator(s); for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { // process c ... }
Если у вас есть Guava на пути к classу, следующее является довольно читаемой альтернативой. В этом случае Guava даже имеет довольно разумную реализацию Custom 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
Если вам нужно итерации через кодовые точки String
(см. Этот ответ ), более короткий / более читаемый способ – использовать метод CharSequence#codePoints
добавленный в Java 8:
for(int c : string.codePoints().toArray()){ ... }
или используя stream непосредственно вместо цикла for:
string.codePoints().forEach(c -> ...);
Есть также CharSequence#chars
если вы хотите stream символов (хотя это IntStream
, так как нет CharStream
).
В 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
как указано в документе :
Возвращает stream int zero, расширяющий значения char из этой последовательности. Любой символ, который сопоставляется суррогатной кодовой точке, передается через неинтерпретируемый. Если последовательность мутируется во время чтения streamа, результат не определен.
Метод codePoints()
также возвращает IntStream
в соответствии с документом:
Возвращает stream значений кодовой точки из этой последовательности. Любые суррогатные пары, встречающиеся в последовательности, объединены, как если бы с помощью Character.toCodePoint, и результат передается streamу. Любые другие единицы кода, включая обычные символы BMP, непарные суррогаты и неопределенные кодовые единицы, имеют нулевое расширение до значений int, которые затем передаются в stream.
Как отличается символ char и code? Как упоминалось в этой статье:
В Unicode 3.1 добавлены дополнительные символы, в результате чего общее количество символов превышает 216 символов, которые можно выделить одним 16-битным
char
. Следовательно, значениеchar
больше не имеет взаимно однозначного соответствия фундаментальной семантической единице в Unicode. JDK 5 был обновлен для поддержки большего набора значений символов. Вместо изменения определения типаchar
некоторые из новых дополнительных символов представлены суррогатной парой из двух значенийchar
. Чтобы уменьшить путаницу в именах, кодовая точка будет использоваться для обозначения числа, которое представляет конкретный символ Юникода, включая дополнительные.
Наконец, почему forEachOrdered
а не forEach
?
Поведение forEach
явно недетерминировано, когда forEachOrdered
выполняет действие для каждого элемента этого streamа, в порядке обнаружения streamа, если stream имеет определенный порядок forEachOrdered
. Поэтому forEach
не гарантирует, что заказ будет сохранен. Также проверьте этот вопрос для получения дополнительной информации.
Для разницы между символом, точкой кода, глифом и графемой проверьте этот вопрос .
Я бы не использовал StringTokenizer
поскольку это один из classов в JDK, который является наследием.
Джавадок говорит:
StringTokenizer
– это унаследованный class, который сохраняется по соображениям совместимости, хотя его использование не рекомендуется в новом коде. Рекомендуется, чтобы любой, кто ищет эту функциональность, использовал метод split дляString
или пакетjava.util.regex
.
См . Учебники 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 не использует регулярные выражения, и нет строки разделителя, которую вы можете указать, которая не будет содержать ничего между символами. Существует один милый маленький хак, который вы можете использовать для достижения того же: используйте строку как строку разделителя (делая каждый символ в ней разделителем) и возвращайте разделители:
StringTokenizer st = new StringTokenizer(str, str, true);
Однако я упоминаю только эти варианты с целью их увольнения. Оба метода ломают исходную строку в односимвольные строки вместо примитивов char, и оба связаны с большими накладными расходами в виде создания объектов и строковых манипуляций. Сравните это с вызовом charAt () в цикле for, который фактически не наносит накладных расходов.
Разрабатываем этот ответ и этот ответ .
В приведенных выше ответах указывается на проблему многих решений, которые не повторяются по кодовой точке – у них возникнут проблемы с любыми суррогатными символами . В java-документах также описывается проблема (см. «Представления символов Unicode»). Во всяком случае, вот какой-то код, который использует некоторые реальные суррогатные символы из дополнительного набора Unicode и преобразует их обратно в String. Обратите внимание, что .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 map = new HashMap(); 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); } }