Em Java, qual seria a maneira mais rápida de iterar todos os caracteres em uma String?
String str = "a really, really long string";
for (int i = 0, n = str.length(); i < n; i++) {
char c = str.charAt(i);
}
Ou isto:
char[] chars = str.toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
char c = chars[i];
}
EDIT:
O que eu gostaria de saber é se o custo de chamar o charAt
método repetidamente durante uma iteração longa acaba sendo menor ou maior que o custo de executar uma única chamada toCharArray
no início e acessar diretamente a matriz durante a iteração.
Seria ótimo se alguém pudesse fornecer uma referência robusta para diferentes comprimentos de string, tendo em mente o tempo de aquecimento do JIT, o tempo de inicialização da JVM, etc., e não apenas a diferença entre duas chamadas para System.currentTimeMillis()
.
for (char c : chars)
?charAt
acaba sendo menor ou maior que o custo de realizar uma única ligação paratoCharArray
Respostas:
PRIMEIRA ATUALIZAÇÃO: Antes de tentar isso em um ambiente de produção (não recomendado), leia primeiro: http://www.javaspecialists.eu/archive/Issue237.html A partir do Java 9, a solução descrita não funcionará mais , porque agora o Java armazenará seqüências de caracteres como byte [] por padrão.
SEGUNDA ATUALIZAÇÃO: A partir de 25/10/2016, no meu AMDx64 8core e na fonte 1.8, não há diferença entre usar 'charAt' e acesso ao campo. Parece que a jvm está suficientemente otimizada para alinhar e otimizar qualquer chamada 'string.charAt (n)'.
Tudo depende da duração da
String
inspeção. Se, como diz a pergunta, for para cadeias longas , a maneira mais rápida de inspecionar a cadeia é usar a reflexão para acessar o respaldochar[]
da cadeia.Um benchmark totalmente randomizado com JDK 8 (win32 e win64) em um AMD Phenom II 4 core 955 a 3.2 GHZ 64 (no modo cliente e no servidor) com 9 técnicas diferentes (veja abaixo!) Mostra que o uso
String.charAt(n)
é o mais rápido para pequenas empresas. strings e o usoreflection
para acessar a matriz de apoio String é quase duas vezes mais rápido para strings grandes.O EXPERIMENTO
São testadas 9 técnicas de otimização diferentes.
Todo o conteúdo da string é randomizado
O teste é feito para tamanhos de string em múltiplos de dois começando com 0,1,2,4,8,16 etc.
Os testes são feitos 1.000 vezes por tamanho de string
Os testes são embaralhados em ordem aleatória a cada vez. Em outras palavras, os testes são feitos em ordem aleatória toda vez que são feitos, mais de 1000 vezes.
Todo o conjunto de testes é feito para a frente e para trás, para mostrar o efeito do aquecimento da JVM na otimização e nos tempos.
Todo o conjunto é feito duas vezes, uma no
-client
modo e a outra no-server
modo.CONCLUSÕES
modo de cliente (32 bits)
Para cadeias de caracteres de 1 a 256 caracteres , a chamada
string.charAt(i)
vence com um processamento médio de 13,4 a 588 milhões de caracteres por segundo.Além disso, é globalmente 5,5% mais rápido (cliente) e 13,9% (servidor) como este:
do que assim com uma variável local de comprimento final:
Para seqüências longas, o tamanho de 512 a 256K caracteres , o uso da reflexão para acessar o array de backup da String é mais rápido. Essa técnica é quase duas vezes mais rápida que String.charAt (i) (178% mais rápida). A velocidade média nesse intervalo foi de 1.111 bilhões de caracteres por segundo.
O campo deve ser obtido com antecedência e, em seguida, pode ser reutilizado na biblioteca em diferentes cadeias. Curiosamente, ao contrário do código acima, com o acesso ao campo, é 9% mais rápido ter uma variável de comprimento final local do que usar 'chars.length' na verificação do loop. Aqui está como o acesso ao campo pode ser configurado o mais rápido:
Comentários especiais no modo -server
O acesso ao campo começa a ganhar depois de 32 caracteres de comprimento no modo servidor em uma máquina Java de 64 bits na minha máquina AMD 64. Isso não foi visto até os 512 caracteres de comprimento no modo cliente.
Também vale a pena notar que, quando eu estava executando o JDK 8 (compilação de 32 bits) no modo de servidor, o desempenho geral foi 7% mais lento para cadeias grandes e pequenas. Isso ocorreu com a versão 121 de dezembro de 2013 da versão inicial do JDK 8. Portanto, por enquanto, parece que o modo de servidor de 32 bits é mais lento que o modo de cliente de 32 bits.
Dito isto ... parece que o único modo de servidor que vale a pena chamar está em uma máquina de 64 bits. Caso contrário, ele realmente prejudica o desempenho.
Para compilação de 32 bits rodando em
-server mode
um AMD64, posso dizer o seguinte:Também vale a pena dizer, String.chars () (Stream e a versão paralela) são um fracasso. Muito mais lento do que qualquer outro caminho. A
Streams
API é uma maneira bastante lenta de executar operações gerais de string.Lista de Desejos
O Java String pode ter um predicado que aceita métodos otimizados, como contains (predicado), forEach (consumidor), forEachWithIndex (consumidor). Portanto, sem a necessidade do usuário saber o tamanho ou repetir as chamadas para os métodos String, isso pode ajudar a analisar a
beep-beep beep
aceleração das bibliotecas .Continue sonhando :)
Cordas felizes!
~ SH
O teste usou os 9 métodos a seguir para testar a cadeia de caracteres quanto à presença de espaço em branco:
"charAt1" - VERIFIQUE O CONTEÚDO DA CORDA DA MANEIRA USUAL:
"charAt2" - O MESMO ACIMA, MAS USE String.length () EM VEZ DE FAZER UM LOCAL FINAL int PARA O COMPRIMENTO
"stream" - USE o IntStream do novo JAVA-8 String e passe um predicado para a verificação
"streamPara" - O MESMO ACIMA, MAS OH-LA-LA - VAI PARALELO !!!
"reutilizar" - recarregue um caractere reutilizável [] COM O CONTEÚDO DAS CORDAS
"new1" - OBTENHA UMA NOVA CÓPIA DO char [] DA STRING
"new2" - O MESMO ACIMA, MAS USE "PARA CADA"
"campo1" - FANTÁSTICO !! OBTENHA UM CAMPO PARA ACESSO AO CARACTER INTERNA DO STRING []
"field2" - O MESMO ACIMA, MAS USAR "PARA CADA"
RESULTADOS COMPOSITOS PARA O
-client
MODO CLIENTE (testes para frente e para trás combinados)Nota: que o modo -client com Java de 32 bits e o modo -server com Java de 64 bits são os mesmos que abaixo na minha máquina AMD64.
RESULTADOS COMPOSITOS PARA O
-server
MODO SERVIDOR (testes para frente e para trás combinados)Nota: este é o teste para Java de 32 bits em execução no modo de servidor em um AMD64. O modo de servidor para Java de 64 bits era igual ao Java de 32 bits no modo de cliente, exceto que o acesso ao campo começou a ganhar após o tamanho de 32 caracteres.
CÓDIGO DE PROGRAMA RUNNABLE COMPLETO
(para testar no Java 7 e versões anteriores, remova os dois testes de fluxos)
fonte
É apenas uma micro-otimização com a qual você não deve se preocupar.
retorna uma cópia das
str
matrizes de caracteres (no JDK, retorna uma cópia dos caracteres chamandoSystem.arrayCopy
).Fora isso,
str.charAt()
apenas verifica se o índice está realmente dentro dos limites e retorna um caractere no índice da matriz.O primeiro não cria memória adicional na JVM.
fonte
Apenas por curiosidade e para comparar com a resposta de Saint Hill.
Se você precisar processar dados pesados, não deverá usar a JVM no modo cliente. O modo cliente não é feito para otimizações.
Vamos comparar os resultados dos benchmarks do @Saint Hill usando uma JVM nos modos Client e Server.
Veja também: Diferenças reais entre "servidor java" e "cliente java"?
MODO CLIENTE:
MODO SERVIDOR:
CONCLUSÃO:
Como você pode ver, o modo servidor é muito mais rápido.
fonte
O primeiro a usar
str.charAt
deve ser mais rápido.Se você digitar dentro do código-fonte da
String
classe, podemos ver quecharAt
é implementado da seguinte maneira:Aqui, tudo o que faz é indexar uma matriz e retornar o valor.
Agora, se observarmos a implementação de
toCharArray
, encontraremos o seguinte:Como você vê, está fazendo um
System.arraycopy
que definitivamente será um pouco mais lento do que não fazê-lo.fonte
Apesar da resposta de @Saint Hill, se você considerar a complexidade de tempo de str.toCharArray () ,
o primeiro é mais rápido, mesmo para cordas muito grandes. Você pode executar o código abaixo para ver por si mesmo.
resultado:
fonte
Parece que niether é mais rápido ou mais lento
Para cordas longas, escolherei a primeira. Por que copiar em seqüências longas? A documentação diz:
// Editar 1
Mudei o teste para enganar a otimização do JIT.
// Editar 2
Repita o teste 10 vezes para permitir que a JVM se aqueça.
// Editar 3
Conclusões:
Primeiro,
str.toCharArray();
copia toda a sequência na memória. Pode consumir memória para seqüências longas. O métodoString.charAt( )
procura char na matriz char dentro da classe String, verificando o índice antes. Parece que, por um curto período de tempo, o primeiro método Strings (chatAt
método) é um pouco mais lento devido a essa verificação de índice. Mas se a String for longa o suficiente, a cópia de toda a matriz de caracteres fica mais lenta e o primeiro método é mais rápido. Quanto mais longa a string, mais lenta será atoCharArray
execução. Tente alterar o limite nofor(int j = 0; j < 10000; j++)
loop para vê-lo. Se permitirmos que o código de aquecimento da JVM seja executado mais rapidamente, mas as proporções são as mesmas.Afinal, é apenas micro-otimização.
fonte
for:in
opção, apenas por diversão?Iterable
nem matriz.String.toCharArray()
cria uma nova matriz de caracteres, significa alocação de memória do comprimento da string, copia a matriz original da string de caracteres usandoSystem.arraycopy()
e retorna essa cópia para o chamador. String.charAt () retorna o caractere na posiçãoi
da cópia original, é por issoString.charAt()
que será mais rápido queString.toCharArray()
. Embora,String.toCharArray()
retorne cópia e não char da matriz String original, ondeString.charAt()
retorna caracteres da matriz char original. O código abaixo retorna o valor no índice especificado dessa sequência.O código abaixo retorna uma matriz de caracteres recém-alocada cujo comprimento é o comprimento dessa string
fonte
O segundo faz com que um novo array de caracteres seja criado, e todos os caracteres da String sejam copiados para esse novo array de caracteres, portanto, acho que o primeiro é mais rápido (e consome menos memória).
fonte