StringTokenizer? Преобразовать String в char[] и повторить это? Что-то другое?
См. Также тесты stackoverflow.com/questions/8894258/…, показывающие, что String.charAt () является самым быстрым для небольших строк, а использование отражения для прямого чтения массива char является самым быстрым для больших строк.
См. Также Как превратить строку в поток в Java?
Java 8: stackoverflow.com/a/47736566/1216775




Я использую цикл for для перебора строки и использую charAt() для проверки каждого символа. Поскольку String реализован с помощью массива, метод charAt() является операцией с постоянным временем.
String s = "...stuff...";
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
//Process char
}
Я бы так и поступил. Мне это кажется самым простым.
Что касается правильности, я не верю, что она здесь существует. Все основано на вашем личном стиле.
Встраивает ли компилятор метод length ()?
Я не знаю. Обычно я не оптимизирую свой код. Но не помешает вывести длину в переменную и использовать ее вместо этого. Я предполагаю, что компилятор встраивает вызов.
@Uri, компилятор Java не выполняет оптимизацию. Для HotSpot JVM довольно скоро встроит его во время выполнения. Существуют и другие реализации JVM (то есть некоторые виртуальные машины J2ME, используемые в телефонах), которые не оптимизируют время выполнения.
он может встроить length (), то есть поднять метод, который вызывает несколько кадров, но это более эффективно для (int i = 0, n = s.length (); i <n; i ++) {char c = s.charAt (i); }
Загромождение кода для увеличения производительности крошечный. Избегайте этого, пока не решите, что эта область кода критична по скорости.
Обычно я не оптимизирую свой код, если не жертвуют удобочитаемостью.
Обратите внимание, что этот метод дает вам символы, а не кодовые точки, что означает, что вы можете получить суррогаты.
charAt - это не O (1) - это O (N) для суррогатов.
@slim: Какого беспорядка вы советуете избегать - кеширование длины с помощью n? Или использовать цикл i вместо конструкции for-each?
@larsH в этом случае я говорил о n, но я также обычно кодировал конструкцию, которая также не использовала i.
@ikh charAt не O (1): Как так? Код для String.charAt(int) просто выполняет value[index]. Я думаю, вы путаете chatAt() с чем-то еще, что дает вам кодовые отметки.
что, если длина String больше, чем диапазон int?
@Indermalviya максимальная длина строки - Integer.MAX_VALUE
Я бы не стал использовать StringTokenizer, поскольку это один из устаревших классов JDK.
В javadoc говорится:
StringTokenizeris a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method ofStringor thejava.util.regexpackage instead.
Строковый токенизатор - совершенно допустимый (и более эффективный) способ перебора токенов (т.е. слов в предложении). Это определенно излишек для перебора символов. Я считаю, что ваш комментарий вводит в заблуждение.
ddimitrov: Я не понимаю, как указывать на то, что StringTokenizer не рекомендуется ВКЛЮЧАТЬ цитату из JavaDoc (java.sun.com/javase/6/docs/api/java/util/StringTokenizer.ht мл), поскольку в нем говорится, что это вводит в заблуждение. Проголосовали за компенсацию.
Спасибо, мистер Бемроуз ... Я полагаю, что процитированная цитата блока должна быть кристально ясной, из чего следует, вероятно, сделать вывод, что активные исправления ошибок не будут внесены в StringTokenizer.
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.
Я начинаю чувствовать себя немного спамерским ... если есть такое слово :). Но это решение также имеет проблему, описанную здесь: Здесь та же проблема, что и здесь: stackoverflow.com/questions/196830/…
Для этого есть несколько специальных классов:
import java.text.*;
final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
// process c
...
}
Похоже на излишество для чего-то столь же простого, как итерация по неизменяемому массиву символов.
Я не понимаю, почему это перебор. Итераторы - это самый java-ish способ делать что-либо ... итеративно. StringCharacterIterator обязан в полной мере использовать неизменность.
Если бы я использовал итератор, я бы тогда использовал цикл foreach.
@jjnguy: foreach возможен только для java.lang.Iterable's
Согласитесь с @ddimitrov - это перебор. Единственная причина использовать итератор - это воспользоваться преимуществом foreach, который немного легче «увидеть», чем цикл for. Если вы все равно собираетесь написать обычный цикл for, то с таким же успехом можно использовать charAt ()
Использование итератора символов, вероятно, является единственным правильным способом перебора символов, потому что Unicode требует больше места, чем предоставляет Java char. Java char содержит 16 бит и может содержать символы Unicode до U + FFFF, но Unicode определяет символы до U + 10FFFF. Использование 16 бит для кодирования Unicode приводит к кодировке символов переменной длины. Большинство ответов на этой странице предполагают, что кодировка Java является кодировкой постоянной длины, что неверно.
@ceving Не похоже, что итератор символов поможет вам с символами, отличными от BMP: oracle.com/us/technologies/java/supplementary-142654.html
Два варианта
for(int i = 0, n = s.length() ; i < n ; i++) {
char c = s.charAt(i);
}
или же
for(char c : s.toCharArray()) {
// process c
}
Первый, вероятно, быстрее, а второй, вероятно, более читабелен.
плюс один для помещения s.length () в выражение инициализации. Если кто-то не знает почему, это потому, что он оценивается только один раз, если он был помещен в оператор завершения как i <s.length (), тогда s.length () будет вызываться каждый раз, когда он зацикливается.
Я думал, что оптимизация компилятора позаботится об этом за вас.
Есть еще мысли по этому поводу? Можем ли мы разумно ожидать, что оптимизация компилятора позаботится о том, чтобы избежать повторного вызова s.length (), или нет?
@Matthias. Вы можете использовать дизассемблер класса Javap, чтобы убедиться, что повторные вызовы s.length () в выражении завершения цикла действительно избегаются. Обратите внимание, что в коде, опубликованном OP, вызов s.length () находится в выражении инициализации, поэтому семантика языка уже гарантирует, что он будет вызван только один раз.
Также см. stackoverflow.com/questions/196830/…
@prasopes Обратите внимание, что большинство оптимизаций java происходит во время выполнения, а НЕ в файлах классов. Даже если вы видели повторяющиеся вызовы length (), которые не обязательно указывают на штраф во время выполнения.
@DaveCheney, зачем вам определять 'n = s.length ()' вместо просто '(int i = 0; i <s.length (); i ++) {'?
@Lasse, предполагаемая причина заключается в эффективности - ваша версия вызывает метод length () на каждой итерации, тогда как Дейв вызывает его один раз в инициализаторе. Тем не менее, весьма вероятно, что оптимизатор JIT («как раз вовремя») оптимизирует дополнительный вызов, так что это, скорее всего, только разница в удобочитаемости без реального выигрыша.
И, на мой взгляд, @Steve, на самом деле он менее читабелен, потому что (1) он нетрадиционный, поэтому он будет отвлекать людей, читающих ваш код (как это сделал Лассе и многие другие комментаторы), и (2) он отодвигает объявление от его использования .
Я не понимаю, почему первый может быть быстрее. Я думал, что foreach лучше всего оптимизирован для повышения производительности, не так ли?
toCharArray копирует содержимое String в новый массив, которого вы избегаете использовать charAt с обычным циклом for.
Также, что касается вызова s.length() в инициализаторе, это также преждевременная оптимизация из учебника, которая мешает читаемости ... не так ли?
StringTokenizer совершенно не подходит для задачи разбиения строки на отдельные символы. С String#split() вы можете легко это сделать, используя регулярное выражение, которое ничего не соответствует, например:
String[] theChars = str.split("|");
Но StringTokenizer не использует регулярные выражения, и вы не можете указать строку-разделитель, которая будет соответствовать ничему между символами. является - один симпатичный маленький прием, который вы можете использовать для достижения того же самого: использовать саму строку в качестве строки-разделителя (делая каждый символ в ней разделителем) и заставить ее возвращать разделители:
StringTokenizer st = new StringTokenizer(str, str, true);
Однако я упоминаю эти варианты только для того, чтобы от них отказаться. Оба метода разбивают исходную строку на односимвольные строки, а не на примитивы типа char, и оба связаны с большими накладными расходами в виде создания объектов и манипуляций со строками. Сравните это с вызовом charAt () в цикле for, который практически не требует накладных расходов.
Я согласен, что StringTokenizer здесь излишний. На самом деле я попробовал приведенные выше предложения и не торопился.
Мой тест был довольно простым: создать StringBuilder примерно с миллионом символов, преобразовать его в String и пройти каждый из них с помощью charAt () / после преобразования в массив char / с помощью CharacterIterator тысячу раз (конечно, убедитесь, что сделать что-нибудь со строкой, чтобы компилятор не смог оптимизировать весь цикл :-)).
Результат на моем Powerbook 2,6 ГГц (это Mac :-)) и JDK 1.5:
Поскольку результаты значительно отличаются, самый простой способ также кажется самым быстрым. Интересно, что 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");
Здесь та же проблема, описанная здесь: stackoverflow.com/questions/196830/…
Обратите внимание, что большинство других описанных здесь методов не работают, если вы имеете дело с символами вне 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
Я не понимаю, как вы здесь используете что-либо, кроме Basic Multilingual Plane. curChar по-прежнему прав на 16 бит?
Вы либо используете int для хранения всей кодовой точки, либо каждый char будет хранить только одну из двух суррогатных пар, которые определяют кодовую точку.
Я думаю, мне нужно прочитать кодовые точки и суррогатные пары. Спасибо!
+1, так как это, кажется, единственный ответ, который верен для символов Unicode вне BMP
Написал код, чтобы проиллюстрировать концепцию перебора кодовых точек (в отличие от символов): gist.github.com/EmmanuelOga/…
Важный момент, и его конкретно спрашивают по адресу: stackoverflow.com/questions/1527856/…
Если у вас есть Гуава в вашем пути к классам, следующая альтернатива является довольно удобочитаемой. У Guava даже есть довольно разумная реализация кастомного списка для этого случая, так что это не должно быть неэффективным.
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
Если вам нужно сделать что-то сложное, используйте цикл for + guava, поскольку вы не можете изменять переменные (например, целые числа и строки), определенные вне области forEach внутри forEach. Все, что находится внутри forEach, также не может генерировать проверенные исключения, что также иногда раздражает.
Если вам нужно перебрать кодовые точки String (см. Этот отвечать), более короткий / более читаемый способ - использовать метод CharSequence#codePoints, добавленный в Java 8:
for(int c : string.codePoints().toArray()){
...
}
или используя поток напрямую вместо цикла for:
string.codePoints().forEach(c -> ...);
Также существует CharSequence#chars, если вам нужен поток символов (хотя это IntStream, поскольку CharStream отсутствует).
Прорабатываем этот ответ и этот ответ.
Приведенные выше ответы указывают на проблему многих решений здесь, которые не повторяются по значению кодовой точки - у них будут проблемы с любым суррогатные символы. В документации java также описывается проблема здесь (см. «Представления символов Unicode»). Во всяком случае, вот код, который использует некоторые фактические суррогатные символы из дополнительного набора Unicode и преобразует их назад в String. Обратите внимание, что .toChars () возвращает массив символов: если вы имеете дело с суррогатами, у вас обязательно будет два символа. Этот код должен работать с символом Unicode Любые.
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);
}
}
В 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, как указано в док:
Returns a stream of int zero-extending the char values from this sequence. Any char which maps to a surrogate code point is passed through uninterpreted. If the sequence is mutated while the stream is being read, the result is undefined.
Метод codePoints() также возвращает IntStream согласно документу:
Returns a stream of code point values from this sequence. Any surrogate pairs encountered in the sequence are combined as if by Character.toCodePoint and the result is passed to the stream. Any other code units, including ordinary BMP characters, unpaired surrogates, and undefined code units, are zero-extended to int values which are then passed to the stream.
Чем отличаются char и code point? Как упоминалось в статье это:
Unicode 3.1 added supplementary characters, bringing the total number of characters to more than the 2^16 = 65536 characters that can be distinguished by a single 16-bit
char. Therefore, acharvalue no longer has a one-to-one mapping to the fundamental semantic unit in Unicode. JDK 5 was updated to support the larger set of character values. Instead of changing the definition of thechartype, some of the new supplementary characters are represented by a surrogate pair of twocharvalues. To reduce naming confusion, a code point will be used to refer to the number that represents a particular Unicode character, including supplementary ones.
Наконец, почему forEachOrdered, а не forEach?
Поведение forEach явно недетерминировано, поскольку forEachOrdered выполняет действие для каждого элемента этого потока в порядок встреч в потоке, если поток имеет определенный порядок встречи. Таким образом, forEach не гарантирует соблюдение порядка. Также проверьте этот вопрос для получения дополнительной информации.
Для разница между символом, кодовой точкой, глифом и графемой проверьте это вопрос.
Если вам нужна производительность, тогда вы должен проверить в своей среде. Другого пути нет.
Вот пример кода:
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, на которую уже ответили несколько человек здесь, в этом потоке, просто добавив мою версию. Сначала использует
String s = sc.next() // assuming scanner class is defined above
for(int i=0; i<s.length; i++){
s.charAt(i) // This being the first way and is a constant time operation will hardly add any overhead
}
char[] str = new char[10];
str = s.toCharArray() // this is another way of doing so and it takes O(n) amount of time for copying contents from your string class to character array
Если на карту поставлена производительность, я рекомендую использовать первый в постоянном времени, если нет, то переход со вторым облегчит вашу работу, учитывая неизменность со строковыми классами в java.
См. Также stackoverflow.com/questions/1527856/…