Consulte também stackoverflow.com/questions/8894258/… Benchmarks show String.charAt () é o mais rápido para seqüências de caracteres pequenas, e usar reflexão para ler diretamente a matriz de caracteres é mais rápido para seqüências de caracteres grandes.
Eu uso um loop for para iterar a string e uso charAt()para que cada caractere a examine. Como a String é implementada com uma matriz, o charAt()método é uma operação de tempo constante.
String s ="...stuff...";for(int i =0; i < s.length(); i++){char c = s.charAt(i);//Process char}
Isso é o que eu faria. Parece o mais fácil para mim.
No que diz respeito à correção, não acredito que exista aqui. Tudo é baseado no seu estilo pessoal.
pode inline length (), que é içar o método por trás dessa chamada de alguns quadros, mas é mais eficiente fazer isso para (int i = 0, n = s.length (); i <n; i ++) {char c = s.charAt (i); }
Dave Cheney
32
Organizando seu código para obter um pequeno ganho de desempenho. Evite isso até que você decida que essa área de código é crítica à velocidade.
magro
31
Observe que essa técnica fornece caracteres , não pontos de código , o que significa que você pode obter substitutos.
Gabe
2
@ikh charAt não é O (1) : como é isso? O código para String.charAt(int)é apenas fazer value[index]. Eu acho que você está confundindo chatAt()com outra coisa que lhe dá pontos de código.
antak
208
Duas opções
for(int i =0, n = s.length(); i < n ; i++){char c = s.charAt(i);}
ou
for(char c : s.toCharArray()){// process c}
O primeiro é provavelmente mais rápido, e o segundo é provavelmente mais legível.
mais um para colocar o s.length () na expressão de inicialização. Se alguém não sabe o porquê, é porque isso é avaliado apenas uma vez em que se foi colocado na instrução de terminação como i <s.length (), então s.length () será chamado toda vez que ele fizer um loop.
Dennis
57
Eu pensei que a otimização do compilador cuidava disso para você.
Rhyous
4
@ Matthias Você pode usar o desmontador da classe Javap para ver se as chamadas repetidas para s.length () na expressão de terminação de loop são realmente evitadas. Observe que no código OP postado a chamada para s.length () está na expressão de inicialização, portanto a semântica do idioma já garante que será chamada apenas uma vez.
Prasopes
3
@prasopes Observe que a maioria das otimizações de java acontece no tempo de execução, NÃO nos arquivos de classe. Mesmo se você viu chamadas repetidas para length () que não indicam uma penalidade de tempo de execução, necessariamente.
Isaac
2
@Lasse, o motivo putativo é a eficiência - sua versão chama o método length () em todas as iterações, enquanto Dave o chama uma vez no inicializador. Dito isso, é muito provável que o otimizador JIT ("just in time") otimize a distância extra, portanto é provável que seja apenas uma diferença de legibilidade para nenhum ganho real.
Steve
90
Observe que a maioria das outras técnicas descritas aqui se decompõe se você estiver lidando com caracteres fora do BMP (Unicode Basic Multilingual Plane ), ou seja, pontos de código que estão fora do intervalo u0000-uFFFF. Isso só acontecerá raramente, uma vez que os pontos de código fora disso são atribuídos principalmente a idiomas mortos. Mas existem alguns caracteres úteis fora disso, por exemplo, alguns pontos de código usados para notação matemática e outros usados para codificar nomes próprios em chinês.
Nesse caso, seu código será:
String str ="....";int offset =0, strLen = str.length();while(offset < strLen){int curChar = str.codePointAt(offset);
offset +=Character.charCount(curChar);// do something with curChar}
Não entendo como você usa nada além do Plano Multilíngue Básico aqui. curChar ainda está 16 bits certo?
O contrato do Prof. Falken violou
2
Você usa um int para armazenar o ponto de código inteiro, ou então cada caractere armazena apenas um dos dois pares substitutos que definem o ponto de código.
sk.
11
Eu acho que preciso ler sobre pontos de código e pares substitutos. Obrigado!
O contrato do Prof. Falken violou
6
+1 uma vez que esta parece ser a única resposta que é correto para Unicode carboniza fora do BMP
Jason S
Escreveu algum código para ilustrar o conceito de iteração sobre pontos de código (em oposição a caracteres): gist.github.com/EmmanuelOga/...
Emmanuel OGA
26
Concordo que o StringTokenizer é um exagero aqui. Na verdade, eu tentei as sugestões acima e aproveitei o tempo.
Meu teste foi bastante simples: crie um StringBuilder com cerca de um milhão de caracteres, converta-o em String e percorra cada um deles com charAt () / depois de converter em um array de caracteres / com um CharacterIterator milhares de vezes (é claro, certifique-se de faça algo na string para que o compilador não possa otimizar todo o loop :-)).
O resultado no meu Powerbook de 2,6 GHz (que é um mac :-)) e no JDK 1.5:
Teste 1: charAt + String -> 3138msec
Teste 2: String convertida em array -> 9568msec
Teste 3: StringBuilder charAt -> 3536msec
Teste 4: CharacterIterator e String -> 12151msec
Como os resultados são significativamente diferentes, a maneira mais direta também parece ser a mais rápida. Curiosamente, charAt () de um StringBuilder parece ser um pouco mais lento que o de String.
BTW, sugiro não usar o CharacterIterator, pois considero o abuso do caracter '\ uFFFF' como "final da iteração" um truque realmente terrível. Em grandes projetos, sempre existem dois caras que usam o mesmo tipo de hack para dois propósitos diferentes e o código trava muito misteriosamente.
Aqui está um dos testes:
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");
O método chars () retorna um IntStreamcomo mencionado no doc :
Retorna um fluxo de int estendendo zero os valores de caracteres dessa sequência. Qualquer caractere mapeado para um ponto de código substituto é passado não interpretado. Se a sequência for alterada enquanto o fluxo estiver sendo lido, o resultado será indefinido.
O método codePoints() também retorna um IntStreamconforme o documento:
Retorna um fluxo de valores de pontos de código dessa sequência. Quaisquer pares substitutos encontrados na sequência são combinados como se por Character.toCodePoint e o resultado é passado para o fluxo. Quaisquer outras unidades de código, incluindo caracteres BMP comuns, substitutos não emparelhados e unidades de código indefinidas, são estendidos em zero aos valores int que são passados para o fluxo.
Qual a diferença entre char e code point? Como mencionado em neste artigo:
O Unicode 3.1 adicionou caracteres suplementares, elevando o número total de caracteres para mais do que os 216 caracteres que podem ser distinguidos por um único 16 bits char. Portanto, um charvalor não possui mais um mapeamento individual para a unidade semântica fundamental no Unicode. O JDK 5 foi atualizado para suportar o conjunto maior de valores de caracteres. Em vez de alterar a definição do chartipo, alguns dos novos caracteres suplementares são representados por um par substituto de dois charvalores. Para reduzir a confusão de nomes, um ponto de código será usado para se referir ao número que representa um caractere Unicode específico, incluindo caracteres adicionais.
Finalmente, por que forEachOrderede não forEach?
O comportamento de forEaché explicitamente não determinístico, onde, quando ele forEachOrderedexecuta uma ação para cada elemento desse fluxo, na ordem de encontro do fluxo, se o fluxo tiver uma ordem de encontro definida. Portanto forEach, não garante que o pedido seja mantido. Verifique também esta pergunta para mais informações.
Para a diferença entre um caractere, um ponto de código, um glifo e um grafema, verifique esta questão .
import java.text.*;finalCharacterIterator it =newStringCharacterIterator(s);for(char c = it.first(); c !=CharacterIterator.DONE; c = it.next()){// process c...}
Parece um exagero para algo tão simples quanto iterar sobre uma matriz de caracteres imutável.
Ddimitrov 13/10/08
11
Não vejo por que isso é um exagero. Os iteradores são a maneira mais java-ish de fazer qualquer coisa ... iterativa. O StringCharacterIterator é obrigado a tirar o máximo proveito da imutabilidade.
magro
2
Concorde com @ddimitrov - isso é um exagero. A única razão para usar um iterador seria tirar proveito do foreach, que é um pouco mais fácil de "ver" do que um loop for. Se você quiser escrever um loop for convencional de qualquer maneira, use charAt ()
Rob Gilliam
3
O uso do iterador de caracteres é provavelmente a única maneira correta de iterar sobre os caracteres, porque o Unicode requer mais espaço do que o Java charfornece. Um Java charcontém 16 bits e pode conter caracteres Unicode até U + FFFF, mas o Unicode especifica caracteres até U + 10FFFF. Usar 16 bits para codificar Unicode resulta em uma codificação de caracteres de comprimento variável. A maioria das respostas nesta página assume que a codificação Java é uma codificação de comprimento constante, o que está errado.
Se você possui o Guava no caminho de classe, a seguir é uma alternativa bastante legível. A goiaba ainda tem uma implementação de lista personalizada bastante sensata para esse caso, portanto, isso não deve ser ineficiente.
for(char c :Lists.charactersOf(yourString)){// Do whatever you want }
ATUALIZAÇÃO: Como o @Alex observou, o Java 8 também CharSequence#charsdeve ser usado. Até o tipo é IntStream, portanto pode ser mapeado para caracteres como:
yourString.chars().mapToObj(c ->Character.valueOf((char) c)).forEach(c ->System.out.println(c));// Or whatever you want
Se você precisar fazer algo complexo, vá com o loop for + goiaba, pois você não pode alterar variáveis (por exemplo, Inteiros e Strings) definidas fora do escopo do forEach dentro do forEach. O que quer que esteja dentro do forEach também não pode lançar exceções verificadas, então às vezes é irritante também.
sabujp 28/07/19
13
Se você precisar percorrer os pontos de código de um String (consulte esta resposta ), uma maneira mais curta / mais legível é usar o CharSequence#codePointsmétodo adicionado no Java 8:
for(int c : string.codePoints().toArray()){...}
ou usando o fluxo diretamente em vez de um loop for:
string.codePoints().forEach(c ->...);
Também existe CharSequence#charsse você deseja um fluxo de caracteres (embora seja umIntStream , já que não existe CharStream).
Eu não usaria StringTokenizer , pois é uma das classes no JDK que é herdada.
O javadoc diz:
StringTokenizeré uma classe herdada que é mantida por motivos de compatibilidade, embora seu uso seja desencorajado em novo código. Recomenda-se que qualquer pessoa que procure essa funcionalidade use o método split Stringou o
java.util.regexpacote.
O tokenizador de strings é uma maneira perfeitamente válida (e mais eficiente) para iterar sobre tokens (ou seja, palavras em uma frase.) É definitivamente um exagero para iterar sobre caracteres. Voto seu comentário como enganador.
Ddimitrov 13/10/08
3
ddimitrov: Eu não estou seguindo como apontando que StringTokenizer não é recomendada, incluindo uma citação do JavaDoc ( java.sun.com/javase/6/docs/api/java/util/StringTokenizer.html ) para ele declarar como tal, é enganoso. Votado para compensar.
Powerlord
11
Obrigado, Sr. Bemrose ... Entendo que a citação de bloco citada deveria ter sido clara, onde provavelmente se deve inferir que as correções de bugs ativas não serão confirmadas no StringTokenizer.
Alan
2
Se você precisar de desempenho, deverá testar em seu ambiente. Não há outro jeito.
Aqui exemplo de código:
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);}}
Estou começando a me sentir um pouco spammerish ... se houver essa palavra :). Mas esta solução também tem o problema descrito aqui: Este tem o mesmo problema descrito aqui: stackoverflow.com/questions/196830/...
Emmanuel Oga
0
StringTokenizer é totalmente inadequado para a tarefa de quebrar uma string em seus caracteres individuais. Com String#split()você, você pode fazer isso facilmente usando uma regex que não corresponde a nada, por exemplo:
String[] theChars = str.split("|");
Mas o StringTokenizer não usa expressões regulares, e não há uma string delimitadora que você possa especificar que corresponda ao nada entre os caracteres. Não é um pouco bonito cortar você pode usar para realizar a mesma coisa: usar a própria string, como a cadeia de delimitador (fazendo com que cada personagem em que um delimitador) e tê-lo retornar os delimitadores:
StringTokenizer st =newStringTokenizer(str, str,true);
No entanto, apenas menciono essas opções com o objetivo de descartá-las. Ambas as técnicas dividem a cadeia original em cadeias de um caractere em vez de primitivas de caracteres, e ambas envolvem uma grande sobrecarga na forma de criação de objetos e manipulação de cadeias. Compare isso com a chamada charAt () em um loop for, que incorre em praticamente nenhuma sobrecarga.
As respostas acima apontam o problema de muitas das soluções aqui que não iteram pelo valor do ponto de código - elas teriam problemas com quaisquer caracteres substitutos . Os documentos em java também descrevem o problema aqui (consulte "Representações de caracteres Unicode"). De qualquer forma, aqui está um código que usa alguns caracteres substitutos reais do conjunto Unicode suplementar e os converte novamente em uma String. Observe que .toChars () retorna uma matriz de caracteres: se você estiver lidando com substitutos, necessariamente terá dois caracteres. Este código deve funcionar para qualquer caractere Unicode.
Então, tipicamente, existem duas maneiras de percorrer a string em java, que já foi respondida por várias pessoas aqui neste tópico, apenas adicionando minha versão dele. First is using
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
Se o desempenho estiver em risco, recomendarei usar o primeiro em tempo constante; caso contrário, o segundo facilita o seu trabalho, considerando a imutabilidade das classes de strings em java.
Respostas:
Eu uso um loop for para iterar a string e uso
charAt()
para que cada caractere a examine. Como a String é implementada com uma matriz, ocharAt()
método é uma operação de tempo constante.Isso é o que eu faria. Parece o mais fácil para mim.
No que diz respeito à correção, não acredito que exista aqui. Tudo é baseado no seu estilo pessoal.
fonte
String.charAt(int)
é apenas fazervalue[index]
. Eu acho que você está confundindochatAt()
com outra coisa que lhe dá pontos de código.Duas opções
ou
O primeiro é provavelmente mais rápido, e o segundo é provavelmente mais legível.
fonte
Observe que a maioria das outras técnicas descritas aqui se decompõe se você estiver lidando com caracteres fora do BMP (Unicode Basic Multilingual Plane ), ou seja, pontos de código que estão fora do intervalo u0000-uFFFF. Isso só acontecerá raramente, uma vez que os pontos de código fora disso são atribuídos principalmente a idiomas mortos. Mas existem alguns caracteres úteis fora disso, por exemplo, alguns pontos de código usados para notação matemática e outros usados para codificar nomes próprios em chinês.
Nesse caso, seu código será:
O
Character.charCount(int)
método requer Java 5+.Fonte: http://mindprod.com/jgloss/codepoint.html
fonte
Concordo que o StringTokenizer é um exagero aqui. Na verdade, eu tentei as sugestões acima e aproveitei o tempo.
Meu teste foi bastante simples: crie um StringBuilder com cerca de um milhão de caracteres, converta-o em String e percorra cada um deles com charAt () / depois de converter em um array de caracteres / com um CharacterIterator milhares de vezes (é claro, certifique-se de faça algo na string para que o compilador não possa otimizar todo o loop :-)).
O resultado no meu Powerbook de 2,6 GHz (que é um mac :-)) e no JDK 1.5:
Como os resultados são significativamente diferentes, a maneira mais direta também parece ser a mais rápida. Curiosamente, charAt () de um StringBuilder parece ser um pouco mais lento que o de String.
BTW, sugiro não usar o CharacterIterator, pois considero o abuso do caracter '\ uFFFF' como "final da iteração" um truque realmente terrível. Em grandes projetos, sempre existem dois caras que usam o mesmo tipo de hack para dois propósitos diferentes e o código trava muito misteriosamente.
Aqui está um dos testes:
fonte
No Java 8 , podemos resolvê-lo da seguinte maneira:
O método chars () retorna um
IntStream
como mencionado no doc :O método
codePoints()
também retorna umIntStream
conforme o documento:Qual a diferença entre char e code point? Como mencionado em neste artigo:
Finalmente, por que
forEachOrdered
e nãoforEach
?O comportamento de
forEach
é explicitamente não determinístico, onde, quando eleforEachOrdered
executa uma ação para cada elemento desse fluxo, na ordem de encontro do fluxo, se o fluxo tiver uma ordem de encontro definida. PortantoforEach
, não garante que o pedido seja mantido. Verifique também esta pergunta para mais informações.Para a diferença entre um caractere, um ponto de código, um glifo e um grafema, verifique esta questão .
fonte
Existem algumas aulas dedicadas para isso:
fonte
char
fornece. Um Javachar
contém 16 bits e pode conter caracteres Unicode até U + FFFF, mas o Unicode especifica caracteres até U + 10FFFF. Usar 16 bits para codificar Unicode resulta em uma codificação de caracteres de comprimento variável. A maioria das respostas nesta página assume que a codificação Java é uma codificação de comprimento constante, o que está errado.Se você possui o Guava no caminho de classe, a seguir é uma alternativa bastante legível. A goiaba ainda tem uma implementação de lista personalizada bastante sensata para esse caso, portanto, isso não deve ser ineficiente.
ATUALIZAÇÃO: Como o @Alex observou, o Java 8 também
CharSequence#chars
deve ser usado. Até o tipo é IntStream, portanto pode ser mapeado para caracteres como:fonte
Se você precisar percorrer os pontos de código de um
String
(consulte esta resposta ), uma maneira mais curta / mais legível é usar oCharSequence#codePoints
método adicionado no Java 8:ou usando o fluxo diretamente em vez de um loop for:
Também existe
CharSequence#chars
se você deseja um fluxo de caracteres (embora seja umIntStream
, já que não existeCharStream
).fonte
Eu não usaria
StringTokenizer
, pois é uma das classes no JDK que é herdada.O javadoc diz:
fonte
Se você precisar de desempenho, deverá testar em seu ambiente. Não há outro jeito.
Aqui exemplo de código:
No Java online , recebo:
No Android x86 API 17, recebo:
fonte
Consulte Os tutoriais de Java: seqüências de caracteres .
Coloque o comprimento
int len
e use ofor
loop.fonte
StringTokenizer é totalmente inadequado para a tarefa de quebrar uma string em seus caracteres individuais. Com
String#split()
você, você pode fazer isso facilmente usando uma regex que não corresponde a nada, por exemplo:Mas o StringTokenizer não usa expressões regulares, e não há uma string delimitadora que você possa especificar que corresponda ao nada entre os caracteres. Não é um pouco bonito cortar você pode usar para realizar a mesma coisa: usar a própria string, como a cadeia de delimitador (fazendo com que cada personagem em que um delimitador) e tê-lo retornar os delimitadores:
No entanto, apenas menciono essas opções com o objetivo de descartá-las. Ambas as técnicas dividem a cadeia original em cadeias de um caractere em vez de primitivas de caracteres, e ambas envolvem uma grande sobrecarga na forma de criação de objetos e manipulação de cadeias. Compare isso com a chamada charAt () em um loop for, que incorre em praticamente nenhuma sobrecarga.
fonte
Elaborando sobre esta resposta e esta resposta .
As respostas acima apontam o problema de muitas das soluções aqui que não iteram pelo valor do ponto de código - elas teriam problemas com quaisquer caracteres substitutos . Os documentos em java também descrevem o problema aqui (consulte "Representações de caracteres Unicode"). De qualquer forma, aqui está um código que usa alguns caracteres substitutos reais do conjunto Unicode suplementar e os converte novamente em uma String. Observe que .toChars () retorna uma matriz de caracteres: se você estiver lidando com substitutos, necessariamente terá dois caracteres. Este código deve funcionar para qualquer caractere Unicode.
fonte
Este código de exemplo irá ajudá-lo!
fonte
Então, tipicamente, existem duas maneiras de percorrer a string em java, que já foi respondida por várias pessoas aqui neste tópico, apenas adicionando minha versão dele. First is using
Se o desempenho estiver em risco, recomendarei usar o primeiro em tempo constante; caso contrário, o segundo facilita o seu trabalho, considerando a imutabilidade das classes de strings em java.
fonte