См. Также stackoverflow.com/questions/8894258/… Тесты показывают, что String.charAt () является самым быстрым для небольших строк, а использование отражения для чтения массива char напрямую для самых больших строк.
Я использую цикл for для итерации строки и использую, charAt()чтобы каждый символ проверял ее. Поскольку String реализован с помощью массива, charAt()метод является операцией с постоянным временем.
String s ="...stuff...";for(int i =0; i < s.length(); i++){char c = s.charAt(i);//Process char}
Я бы так и сделал. Это кажется самым легким для меня.
Что касается правильности, я не верю, что это существует здесь. Все это основано на вашем личном стиле.
он может быть встроенным length (), то есть поднять метод, который вызывает несколько кадров, но это более эффективно сделать для (int i = 0, n = s.length (); i <n; i ++) {char c = s.charAt (i); }
Дейв Чейни
32
Загромождение вашего кода для небольшого увеличения производительности. Пожалуйста, избегайте этого, пока не решите, что эта область кода критична для скорости.
тонкий
31
Обратите внимание, что эта техника дает вам символы , а не кодовые точки , то есть вы можете получить суррогаты.
Гейб
2
@ikh charAt это не O (1) : Как это так? Код String.charAt(int)просто делает value[index]. Я думаю, что вы путаете chatAt()с чем-то еще, что дает вам кодовые очки.
Антак
209
Два варианта
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 () будет вызываться каждый раз, когда выполняется цикл.
Деннис
57
Я думал, что оптимизация компилятора позаботилась об этом за вас.
Rhyous
4
@Matthias Вы можете использовать дизассемблер класса Javap, чтобы увидеть, что повторные вызовы s.length () для выражения завершения цикла действительно исключены. Обратите внимание, что в коде OP опубликовано обращение к s.length () в выражении инициализации, поэтому семантика языка уже гарантирует, что он будет вызван только один раз.
прасопы
3
@prasopes Обратите внимание, что большинство оптимизаций Java происходят во время выполнения, а не в файлах классов. Даже если вы видели повторные вызовы length (), которые не указывают штраф за время выполнения, обязательно.
Исаак
2
@Lasse, предполагаемая причина в эффективности - ваша версия вызывает метод length () на каждой итерации, тогда как Дейв вызывает его один раз в инициализаторе. Тем не менее, вполне вероятно, что оптимизатор JIT («как раз вовремя») оптимизирует дополнительный вызов, так что, скорее всего, это только разница в удобочитаемости без реального выигрыша.
Стив
90
Обратите внимание, что большинство других методов, описанных здесь, ломаются, если вы имеете дело с символами вне 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}
Я не понимаю, как вы используете что-либо, кроме базовой многоязычной плоскости здесь. curChar все еще 16 бит правильно?
Профессор Фалькен нарушил контракт
2
Вы либо используете int для хранения всей кодовой точки, либо каждый символ будет хранить только одну из двух суррогатных пар, которые определяют кодовую точку.
ск.
1
Я думаю, что мне нужно прочитать кодовые точки и суррогатные пары. Спасибо!
Проф. Фалькен нарушил контракт
6
+1, так как это, кажется, единственный ответ, который является правильным для символов Unicode за пределами BMP
Джейсон С
Написал некоторый код, чтобы проиллюстрировать концепцию итерации по кодовым точкам
Эммануэль Ога
26
Я согласен, что StringTokenizer здесь перебор. На самом деле я опробовал предложения выше и не торопился.
Мой тест был довольно прост: создать StringBuilder с около миллиона символов, преобразовать его в строку и перебрать каждый из них с помощью charAt () / после преобразования в массив символов / с CharacterIterator тысячу раз (конечно, убедившись, что сделайте что-нибудь со строкой, чтобы компилятор не мог оптимизировать весь цикл :-)).
Результат на моем Powerbook 2.6 ГГц (это mac :-)) и JDK 1.5:
Тест 1: charAt + String -> 3138 мсек
Тест 2: строка преобразуется в массив -> 9568 мс
Тест 3: символ StringBuilder -> 3536 мсек
Тест 4: CharacterIterator и строка -> 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");
Метод chars () возвращает значение, IntStreamуказанное в документе :
Возвращает поток значений int, расширяющих ноль, из этой последовательности. Любой символ, который отображается на суррогатную кодовую точку, пропускается через интерпретацию. Если последовательность видоизменяется во время чтения потока, результат не определен.
Метод codePoints()также возвращает IntStreamсогласно документу:
Возвращает поток значений кодовой точки из этой последовательности. Любые суррогатные пары, встречающиеся в последовательности, объединяются как бы с помощью Character.toCodePoint, и результат передается в поток. Любые другие единицы кода, включая обычные символы BMP, непарные суррогаты и неопределенные единицы кода, расширяются от нуля до значений int, которые затем передаются в поток.
Чем отличаются символ и код? Как уже упоминалось в этой статье:
В Unicode 3.1 добавлены дополнительные символы, в результате чего общее количество символов превышает 216 символов, которые можно различить одним 16-разрядным char. Поэтому charзначение больше не имеет однозначного сопоставления с основной семантической единицей в Юникоде. JDK 5 был обновлен для поддержки большего набора символьных значений. Вместо изменения определения charтипа, некоторые из новых дополнительных символов представлены суррогатной парой двух charзначений. Чтобы уменьшить путаницу имен, будет использоваться кодовая точка для обозначения номера, представляющего конкретный символ Unicode, включая дополнительные.
Наконец почему forEachOrderedи нет forEach?
Поведение forEachявляется явно недетерминированным, когда as forEachOrderedвыполняет действие для каждого элемента этого потока, в порядке обнаружения потока, если поток имеет определенный порядок встречи. Так forEachчто не гарантирует, что заказ будет сохранен. Также проверьте этот вопрос для получения дополнительной информации.
Для различия между символом, кодовой точкой, глифом и графемой, проверьте этот вопрос .
import java.text.*;finalCharacterIterator it =newStringCharacterIterator(s);for(char c = it.first(); c !=CharacterIterator.DONE; c = it.next()){// process c...}
Выглядит как перебор для чего-то столь же простого, как итерация по неизменяемому массиву символов.
Ддимитров
1
Я не понимаю, почему это излишне. Итераторы - это самый простой способ сделать что-либо ... итеративный. StringCharacterIterator обязан в полной мере использовать неизменность.
тонкий
2
Согласитесь с @ddimitrov - это перебор. Единственная причина использовать итератор - использовать foreach, который немного легче "увидеть", чем цикл for. В любом случае, если вы собираетесь написать обычный цикл for, то можете использовать charAt ()
Роб Гиллиам,
3
Использование символьного итератора, вероятно, является единственным правильным способом перебора символов, поскольку Юникод требует больше места, чем charобеспечивает Java . Java charсодержит 16 бит и может содержать символы Unicode до U + FFFF, но Unicode определяет символы до U + 10FFFF. Использование 16 битов для кодирования Unicode приводит к кодированию символов переменной длины. Большинство ответов на этой странице предполагают, что кодировка Java является кодировкой постоянной длины, что неверно.
Если у вас есть Guava на вашем пути к классам, следующее является довольно удобочитаемой альтернативой. В 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
Если вам нужно сделать что-то сложное, то используйте цикл for + guava, поскольку вы не можете изменять переменные (например, целые числа и строки), определенные вне области действия forEach внутри forEach. Все, что находится внутри forEach, также не может генерировать проверенные исключения, так что иногда это тоже раздражает.
Sabujp
13
Если вам нужно перебрать точки кода String(см. Этот ответ ), более короткий / более читаемый способ - использовать CharSequence#codePointsметод, добавленный в Java 8:
for(int c : string.codePoints().toArray()){...}
или используя поток вместо цикла for:
string.codePoints().forEach(c ->...);
Существует также, CharSequence#charsесли вы хотите, чтобы поток символов (хотя это IntStream, так как нет CharStream).
Я бы не стал использовать, так StringTokenizerкак это один из классов в JDK, который унаследован.
Javadoc говорит:
StringTokenizerявляется устаревшим классом, который сохраняется по соображениям совместимости, хотя его использование не рекомендуется в новом коде. Всем, кто ищет эту функцию, рекомендуется вместо этого использовать метод split Stringили
java.util.regexпакет.
Строковый токенизатор - это совершенно правильный (и более эффективный) способ итерации по токенам (т. Е. Словам в предложении). Это определенно избыточно для итерации по символам. Я считаю ваш комментарий вводящим в заблуждение.
Ддимитров
3
ddimitrov: Я не слежу за тем, чтобы указывать на то, что StringTokenizer не рекомендуется, ВКЛЮЧАЯ цитату из JavaDoc ( java.sun.com/javase/6/docs/api/java/util/StringTokenizer.html ), указав, что это так вводит в заблуждение. Проголосовал за зачет.
Powerlord
1
Спасибо, мистер Бемроуз ... Я полагаю, что цитируемая цитата должна быть кристально чистой, и, вероятно, следует сделать вывод, что активные исправления ошибок не будут переданы в StringTokenizer.
Алан
2
Если вам нужна производительность, вы должны протестировать свою среду. По-другому никак.
Вот пример кода:
int tmp =0;String s =newString(newbyte[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);
publicclassStringDemo{publicstaticvoid main(String[] args){String palindrome ="Dot saw I was Tod";int len = palindrome.length();char[] tempCharArray =newchar[len];char[] charArray =newchar[len];// put original string in an array of charsfor(int i =0; i < len; i++){
tempCharArray[i]= palindrome.charAt(i);}// reverse array of charsfor(int j =0; j < len; j++){
charArray[j]= tempCharArray[len -1- j];}String reversePalindrome =newString(charArray);System.out.println(reversePalindrome);}}
Я начинаю чувствовать себя немного спамерским ... если есть такое слово :). Но у этого решения также есть проблема, изложенная здесь: Здесь есть та же проблема, изложенная здесь: stackoverflow.com/questions/196830/…
Эммануэль Ога
0
StringTokenizer совершенно не подходит для задачи разбивки строки на отдельные символы. С этим String#split()вы можете легко это сделать, используя регулярное выражение, которое ничего не соответствует, например:
String[] theChars = str.split("|");
Но StringTokenizer не использует регулярные выражения, и вы не можете указать строку разделителя, которая будет соответствовать ничему между символами. Там является один милый маленький хак вы можете использовать , чтобы сделать то же самое: использовать саму строку в качестве строки разделителей (делая каждый символ в нем разделителей), они должны вернуть разделители:
StringTokenizer st =newStringTokenizer(str, str,true);
Однако я упоминаю только эти варианты с целью их отклонения. Оба метода разбивают исходную строку на односимвольные строки вместо символьных примитивов, и оба требуют больших накладных расходов в виде создания объекта и манипуляции со строками. Сравните это с вызовом charAt () в цикле for, который практически не требует дополнительных затрат.
Приведенные выше ответы указывают на проблему многих решений, которые здесь не повторяются по значению кодовой точки - у них возникнут проблемы с любыми суррогатными символами . Документы Java также описывают проблему здесь (см. «Представления символов Unicode»). Во всяком случае, вот некоторый код, который использует некоторые фактические суррогатные символы из дополнительного набора Unicode и преобразует их обратно в строку. Обратите внимание, что .toChars () возвращает массив символов: если вы имеете дело с суррогатами, у вас обязательно будет два символа. Этот код должен работать для любого символа Unicode.
Поэтому, как правило, есть два способа перебора строки в java, на которую уже ответили несколько человек в этой теме, просто добавив мою версию. Сначала используется
String s = sc.next()// assuming scanner class is defined abovefor(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 =newchar[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.
Ответы:
Я использую цикл for для итерации строки и использую,
charAt()
чтобы каждый символ проверял ее. Поскольку String реализован с помощью массива,charAt()
метод является операцией с постоянным временем.Я бы так и сделал. Это кажется самым легким для меня.
Что касается правильности, я не верю, что это существует здесь. Все это основано на вашем личном стиле.
источник
String.charAt(int)
просто делаетvalue[index]
. Я думаю, что вы путаетеchatAt()
с чем-то еще, что дает вам кодовые очки.Два варианта
или
Первое, вероятно, быстрее, а второе, вероятно, более читабельно.
источник
Обратите внимание, что большинство других методов, описанных здесь, ломаются, если вы имеете дело с символами вне BMP ( базовая многоязычная плоскость Unicode ), то есть кодовые точки, которые находятся за пределами диапазона u0000-uFFFF. Это случается редко, так как кодовые точки вне этого в основном назначаются мертвым языкам. Но помимо этого есть некоторые полезные символы, например, некоторые кодовые точки, используемые для математической записи, а некоторые используются для кодирования собственных имен на китайском языке.
В этом случае ваш код будет:
Character.charCount(int)
Метод требует Java 5+.Источник: http://mindprod.com/jgloss/codepoint.html
источник
Я согласен, что StringTokenizer здесь перебор. На самом деле я опробовал предложения выше и не торопился.
Мой тест был довольно прост: создать StringBuilder с около миллиона символов, преобразовать его в строку и перебрать каждый из них с помощью charAt () / после преобразования в массив символов / с CharacterIterator тысячу раз (конечно, убедившись, что сделайте что-нибудь со строкой, чтобы компилятор не мог оптимизировать весь цикл :-)).
Результат на моем Powerbook 2.6 ГГц (это mac :-)) и JDK 1.5:
Поскольку результаты значительно отличаются, самый простой способ также кажется самым быстрым. Интересно, что charAt () в StringBuilder кажется немного медленнее, чем в String.
Кстати, я предлагаю не использовать CharacterIterator, так как считаю злоупотребление символом '\ uFFFF' как «конец итерации» действительно ужасным хаком. В больших проектах всегда есть два парня, которые используют один и тот же вид взлома для двух разных целей, и код действительно таинственно падает.
Вот один из тестов:
источник
В Java 8 мы можем решить это как:
Метод chars () возвращает значение,
IntStream
указанное в документе :Метод
codePoints()
также возвращаетIntStream
согласно документу:Чем отличаются символ и код? Как уже упоминалось в этой статье:
Наконец почему
forEachOrdered
и нетforEach
?Поведение
forEach
является явно недетерминированным, когда asforEachOrdered
выполняет действие для каждого элемента этого потока, в порядке обнаружения потока, если поток имеет определенный порядок встречи. ТакforEach
что не гарантирует, что заказ будет сохранен. Также проверьте этот вопрос для получения дополнительной информации.Для различия между символом, кодовой точкой, глифом и графемой, проверьте этот вопрос .
источник
Для этого есть несколько специальных классов:
источник
char
обеспечивает Java . Javachar
содержит 16 бит и может содержать символы Unicode до U + FFFF, но Unicode определяет символы до U + 10FFFF. Использование 16 битов для кодирования Unicode приводит к кодированию символов переменной длины. Большинство ответов на этой странице предполагают, что кодировка Java является кодировкой постоянной длины, что неверно.Если у вас есть Guava на вашем пути к классам, следующее является довольно удобочитаемой альтернативой. В Guava даже есть довольно разумная реализация List для этого случая, так что это не должно быть неэффективно.
ОБНОВЛЕНИЕ: Как отметил @Alex, с Java 8 также есть,
CharSequence#chars
что использовать. Даже типом является IntStream, поэтому он может быть сопоставлен с такими символами:источник
Если вам нужно перебрать точки кода
String
(см. Этот ответ ), более короткий / более читаемый способ - использоватьCharSequence#codePoints
метод, добавленный в Java 8:или используя поток вместо цикла for:
Существует также,
CharSequence#chars
если вы хотите, чтобы поток символов (хотя этоIntStream
, так как нетCharStream
).источник
Я бы не стал использовать, так
StringTokenizer
как это один из классов в JDK, который унаследован.Javadoc говорит:
источник
Если вам нужна производительность, вы должны протестировать свою среду. По-другому никак.
Вот пример кода:
На Java онлайн я получаю:
На Android x86 API 17 я получаю:
источник
См . Учебные руководства Java: Строки .
Вставьте длину
int len
и используйтеfor
петлю.источник
StringTokenizer совершенно не подходит для задачи разбивки строки на отдельные символы. С этим
String#split()
вы можете легко это сделать, используя регулярное выражение, которое ничего не соответствует, например:Но StringTokenizer не использует регулярные выражения, и вы не можете указать строку разделителя, которая будет соответствовать ничему между символами. Там является один милый маленький хак вы можете использовать , чтобы сделать то же самое: использовать саму строку в качестве строки разделителей (делая каждый символ в нем разделителей), они должны вернуть разделители:
Однако я упоминаю только эти варианты с целью их отклонения. Оба метода разбивают исходную строку на односимвольные строки вместо символьных примитивов, и оба требуют больших накладных расходов в виде создания объекта и манипуляции со строками. Сравните это с вызовом charAt () в цикле for, который практически не требует дополнительных затрат.
источник
Разрабатывая этот ответ и этот ответ .
Приведенные выше ответы указывают на проблему многих решений, которые здесь не повторяются по значению кодовой точки - у них возникнут проблемы с любыми суррогатными символами . Документы Java также описывают проблему здесь (см. «Представления символов Unicode»). Во всяком случае, вот некоторый код, который использует некоторые фактические суррогатные символы из дополнительного набора Unicode и преобразует их обратно в строку. Обратите внимание, что .toChars () возвращает массив символов: если вы имеете дело с суррогатами, у вас обязательно будет два символа. Этот код должен работать для любого символа Unicode.
источник
Этот пример кода поможет вам!
источник
Поэтому, как правило, есть два способа перебора строки в java, на которую уже ответили несколько человек в этой теме, просто добавив мою версию. Сначала используется
Если на карту поставлена производительность, я порекомендую использовать первый в постоянное время, а если нет, то второй будет облегчать вашу работу, учитывая неизменность с помощью строковых классов в Java.
источник