Maneira mais rápida de iterar todos os caracteres em uma string

Em Java, qual seria a maneira mais rápida de iterar todos os caracteres em uma String, isto:

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]; } 

EDITAR:

O que eu gostaria de saber é se o custo de chamar repetidamente o método charAt durante uma iteração longa acaba sendo menor ou maior que o custo de realizar uma única chamada para toCharArray no início e depois acessar diretamente o array durante o processo. iteração.

Seria ótimo se alguém pudesse fornecer um benchmark robusto para diferentes comprimentos de string, tendo em mente o tempo de aquecimento JIT, o tempo de boot da JVM, etc. e não apenas a diferença entre duas chamadas para System.currentTimeMillis() .

PRIMEIRA ATUALIZAÇÃO: Antes de tentar isso sempre 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 Java irá armazenar seqüências de caracteres como byte [] por padrão.

SEGUNDA ATUALIZAÇÃO: A partir de 2016-10-25, no meu AMDx64 8core e source 1.8, não há diferença entre usar ‘charAt’ e access de campo. Parece que o jvm é suficientemente otimizado para inline e simplificar quaisquer chamadas ‘string.charAt (n)’.

Tudo depende do comprimento da String sendo inspecionada. Se, como a pergunta diz, é para strings longas , a maneira mais rápida de inspecionar a string é usar a reflection para acessar o char[] apoio char[] da string.

Um benchmark totalmente randomizado com o JDK 8 (win32 e win64) em um 64 Phenom II 4 core 955 @ 3.2 GHZ (em modo cliente e modo servidor) com 9 técnicas diferentes (veja abaixo!) Mostra que usando String.charAt(n) é o mais rápido para strings pequenas e o uso da reflection para acessar a matriz de backing de string é quase duas vezes mais rápido para strings grandes.

O EXPERIMENTO

  • 9 técnicas de otimização diferentes são tentadas.

  • 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 de cada vez. Em outras palavras, os testes são feitos em ordem aleatória toda vez que são feitos, mais de 1000 vezes.

  • O conjunto de testes inteiro é 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 vez no modo de -client e o outro no modo de -server .

CONCLUSÕES

modo de cliente (32 bits)

Para strings de 1 a 256 caracteres de comprimento , chamar string.charAt(i) ganha com um processamento médio de 13,4 milhões a 588 milhões de caracteres por segundo.

Além disso, é globalmente 5,5% mais rápido (cliente) e 13,9% (servidor) como este:

  for (int i = 0; i < data.length(); i++) { if (data.charAt(i) <= ' ') { doThrow(); } } 

do que assim com uma variável de comprimento final local:

  final int len = data.length(); for (int i = 0; i < len; i++) { if (data.charAt(i) <= ' ') { doThrow(); } } 

Para strings longas, de 512 a 256K caracteres , o uso da reflection para acessar a matriz de apoio 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ão de caracteres por segundo.

O campo deve ser obtido antes do tempo e, em seguida, ele pode ser reutilizado na biblioteca em diferentes seqüências. Curiosamente, ao contrário do código acima, com o access ao campo, é 9% mais rápido ter uma variável de comprimento final local do que usar 'chars.length' na verificação de loop. Aqui está como o access ao campo pode ser configurado como o mais rápido:

  final Field field = String.class.getDeclaredField("value"); field.setAccessible(true); try { final char[] chars = (char[]) field.get(data); final int len = chars.length; for (int i = 0; i < len; i++) { if (chars[i] <= ' ') { doThrow(); } } return len; } catch (Exception ex) { throw new RuntimeException(ex); } 

Comentários especiais no modo de servidor

Acesso de campo começando a ganhar depois de strings de 32 caracteres em modo de servidor em uma máquina Java de 64 bits em minha máquina AMD 64. Isso não foi visto até 512 caracteres no modo cliente.

Também digno de nota, acho que, quando eu estava executando o JDK 8 (32 bits) no modo de servidor, o desempenho geral foi 7% mais lento para strings grandes e pequenas. Isso foi com a versão 121 de dezembro de 2013 do lançamento antecipado do JDK 8. Então, 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 invocar é em uma máquina de 64 bits. Caso contrário, isso realmente dificulta o desempenho.

Para a compilation de 32 bits em execução no -server mode em um AMD64, posso dizer o seguinte:

  1. String.charAt (i) é o vencedor claro em geral. Embora entre os tamanhos 8 a 512 caracteres, havia vencedores entre 'novo' 'reutilização' e 'campo'.
  2. String.charAt (i) é 45% mais rápido no modo cliente
  3. O access ao campo é duas vezes mais rápido para grandes strings no modo cliente.

Também vale a pena dizer, String.chars () (Stream e a versão paralela) são um fracasso. Muito mais devagar do que qualquer outro caminho. A API do Streams é uma maneira bastante lenta de executar operações gerais de string.

Lista de Desejos

O Java String poderia ter o predicado aceitando methods otimizados, como contains (predicado), forEach (consumidor), forEachWithIndex (consumidor). Assim, sem a necessidade de o usuário saber o tamanho ou repetir as chamadas para os methods String, eles podem ajudar a analisar as bibliotecas de análise de beep-beep beep .

Continue sonhando 🙂

Cordas Felizes!

~ SH

O teste usou os 9 methods a seguir para testar a string quanto à presença de espaço em branco:

"charAt1" - VERIFIQUE OS CONTEÚDOS DA CAMISA O MODO USUAL:

 int charAtMethod1(final String data) { final int len = data.length(); for (int i = 0; i < len; i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return len; } 

"charAt2" - MESMO ACIMA MAS USAR String.length () INSTEAD DE FAZER UM FINAL LOCAL int PARA O LENGTH

 int charAtMethod2(final String data) { for (int i = 0; i < data.length(); i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return data.length(); } 

"stream" - USE O NOVO IntStream da JAVA-8 String E PASSE-O PRECISO PARA FAZER A VERIFICAÇÃO

 int streamMethod(final String data, final IntPredicate predicate) { if (data.chars().anyMatch(predicate)) { doThrow(); } return data.length(); } 

"streamPara" - MESMO ACIMA, MAS OH-LA-LA - PARALELO !!!

 // avoid this at all costs int streamParallelMethod(final String data, IntPredicate predicate) { if (data.chars().parallel().anyMatch(predicate)) { doThrow(); } return data.length(); } 

"reutilizar" - REINICIAR UM char REUSÁVEL [] COM O CONTEÚDO DAS CORDAS

 int reuseBuffMethod(final char[] reusable, final String data) { final int len = data.length(); data.getChars(0, len, reusable, 0); for (int i = 0; i < len; i++) { if (reusable[i] <= ' ') { doThrow(); } } return len; } 

"new1" - OBTENHA UMA NOVA CÓPIA DO char [] FROM THE STRING

 int newMethod1(final String data) { final int len = data.length(); final char[] copy = data.toCharArray(); for (int i = 0; i < len; i++) { if (copy[i] <= ' ') { doThrow(); } } return len; } 

"new2" - MESMO QUE ACIMA, MAS USE "PARA CADA"

 int newMethod2(final String data) { for (final char c : data.toCharArray()) { if (c < = ' ') { doThrow(); } } return data.length(); } 

"field1" - FANTASIA !! OBTER CAMPO PARA ACESSO AO CAR DE INTERNA DA STRING

 int fieldMethod1(final Field field, final String data) { try { final char[] chars = (char[]) field.get(data); final int len = chars.length; for (int i = 0; i < len; i++) { if (chars[i] <= ' ') { doThrow(); } } return len; } catch (Exception ex) { throw new RuntimeException(ex); } } 

"field2" - MESMO QUE ACIMA, MAS USE "PARA CADA"

 int fieldMethod2(final Field field, final String data) { final char[] chars; try { chars = (char[]) field.get(data); } catch (Exception ex) { throw new RuntimeException(ex); } for (final char c : chars) { if (c < = ' ') { doThrow(); } } return chars.length; } 

RESULTADOS COMPOSTOS PARA CLIENTE -client MODO DE CLIENTE (testes para a frente e para trás combinados)

Nota: que o modo -client com Java 32 bits e modo -server com Java 64 bits são os mesmos que abaixo na minha máquina AMD64.

 Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2 1 charAt 77.0 72.0 462.0 584.0 127.5 89.5 86.0 159.5 165.0 2 charAt 38.0 36.5 284.0 32712.5 57.5 48.3 50.3 89.0 91.5 4 charAt 19.5 18.5 458.6 3169.0 33.0 26.8 27.5 54.1 52.6 8 charAt 9.8 9.9 100.5 1370.9 17.3 14.4 15.0 26.9 26.4 16 charAt 6.1 6.5 73.4 857.0 8.4 8.2 8.3 13.6 13.5 32 charAt 3.9 3.7 54.8 428.9 5.0 4.9 4.7 7.0 7.2 64 charAt 2.7 2.6 48.2 232.9 3.0 3.2 3.3 3.9 4.0 128 charAt 2.1 1.9 43.7 138.8 2.1 2.6 2.6 2.4 2.6 256 charAt 1.9 1.6 42.4 90.6 1.7 2.1 2.1 1.7 1.8 512 field1 1.7 1.4 40.6 60.5 1.4 1.9 1.9 1.3 1.4 1,024 field1 1.6 1.4 40.0 45.6 1.2 1.9 2.1 1.0 1.2 2,048 field1 1.6 1.3 40.0 36.2 1.2 1.8 1.7 0.9 1.1 4,096 field1 1.6 1.3 39.7 32.6 1.2 1.8 1.7 0.9 1.0 8,192 field1 1.6 1.3 39.6 30.5 1.2 1.8 1.7 0.9 1.0 16,384 field1 1.6 1.3 39.8 28.4 1.2 1.8 1.7 0.8 1.0 32,768 field1 1.6 1.3 40.0 26.7 1.3 1.8 1.7 0.8 1.0 65,536 field1 1.6 1.3 39.8 26.3 1.3 1.8 1.7 0.8 1.0 131,072 field1 1.6 1.3 40.1 25.4 1.4 1.9 1.8 0.8 1.0 262,144 field1 1.6 1.3 39.6 25.2 1.5 1.9 1.9 0.8 1.0 

RESULTADOS COMPOSTOS PARA SERVIDOR - MODO -server (testes para frente e para trás combinados)

Nota: este é o teste para o Java 32 bit em execução no modo de servidor em um AMD64. O modo de servidor para Java 64 bits era o mesmo que o Java 32 bits no modo cliente, exceto pelo fato de o access ao campo começar a ganhar após o tamanho de 32 caracteres.

 Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2 1 charAt 74.5 95.5 524.5 783.0 90.5 102.5 90.5 135.0 151.5 2 charAt 48.5 53.0 305.0 30851.3 59.3 57.5 52.0 88.5 91.8 4 charAt 28.8 32.1 132.8 2465.1 37.6 33.9 32.3 49.0 47.0 8 new2 18.0 18.6 63.4 1541.3 18.5 17.9 17.6 25.4 25.8 16 new2 14.0 14.7 129.4 1034.7 12.5 16.2 12.0 16.0 16.6 32 new2 7.8 9.1 19.3 431.5 8.1 7.0 6.7 7.9 8.7 64 reuse 6.1 7.5 11.7 204.7 3.5 3.9 4.3 4.2 4.1 128 reuse 6.8 6.8 9.0 101.0 2.6 3.0 3.0 2.6 2.7 256 field2 6.2 6.5 6.9 57.2 2.4 2.7 2.9 2.3 2.3 512 reuse 4.3 4.9 5.8 28.2 2.0 2.6 2.6 2.1 2.1 1,024 charAt 2.0 1.8 5.3 17.6 2.1 2.5 3.5 2.0 2.0 2,048 charAt 1.9 1.7 5.2 11.9 2.2 3.0 2.6 2.0 2.0 4,096 charAt 1.9 1.7 5.1 8.7 2.1 2.6 2.6 1.9 1.9 8,192 charAt 1.9 1.7 5.1 7.6 2.2 2.5 2.6 1.9 1.9 16,384 charAt 1.9 1.7 5.1 6.9 2.2 2.5 2.5 1.9 1.9 32,768 charAt 1.9 1.7 5.1 6.1 2.2 2.5 2.5 1.9 1.9 65,536 charAt 1.9 1.7 5.1 5.5 2.2 2.4 2.4 1.9 1.9 131,072 charAt 1.9 1.7 5.1 5.4 2.3 2.5 2.5 1.9 1.9 262,144 charAt 1.9 1.7 5.1 5.1 2.3 2.5 2.5 1.9 1.9 

CÓDIGO DO PROGRAMA COMPLETO

(para testar no Java 7 e versões anteriores, remova os dois testes de streams)

 import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.function.IntPredicate; /** * @author Saint Hill  */ public final class TestStrings { // we will not test strings longer than 512KM final int MAX_STRING_SIZE = 1024 * 256; // for each string size, we will do all the tests // this many times final int TRIES_PER_STRING_SIZE = 1000; public static void main(String[] args) throws Exception { new TestStrings().run(); } void run() throws Exception { // double the length of the data until it reaches MAX chars long // 0,1,2,4,8,16,32,64,128,256 ... final List sizes = new ArrayList<>(); for (int n = 0; n < = MAX_STRING_SIZE; n = (n == 0 ? 1 : n * 2)) { sizes.add(n); } // CREATE RANDOM (FOR SHUFFLING ORDER OF TESTS) final Random random = new Random(); System.out.println("Rate in nanoseconds per character inspected."); System.out.printf("==== FORWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE); printHeadings(TRIES_PER_STRING_SIZE, random); for (int size : sizes) { reportResults(size, test(size, TRIES_PER_STRING_SIZE, random)); } // reverse order or string sizes Collections.reverse(sizes); System.out.println(""); System.out.println("Rate in nanoseconds per character inspected."); System.out.printf("==== BACKWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE); printHeadings(TRIES_PER_STRING_SIZE, random); for (int size : sizes) { reportResults(size, test(size, TRIES_PER_STRING_SIZE, random)); } } /// /// /// METHODS OF CHECKING THE CONTENTS /// OF A STRING. ALWAYS CHECKING FOR /// WHITESPACE (CHAR <=' ') /// /// // CHECK THE STRING CONTENTS int charAtMethod1(final String data) { final int len = data.length(); for (int i = 0; i < len; i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return len; } // SAME AS ABOVE BUT USE String.length() // instead of making a new final local int int charAtMethod2(final String data) { for (int i = 0; i < data.length(); i++) { if (data.charAt(i) <= ' ') { doThrow(); } } return data.length(); } // USE new Java-8 String's IntStream // pass it a PREDICATE to do the checking int streamMethod(final String data, final IntPredicate predicate) { if (data.chars().anyMatch(predicate)) { doThrow(); } return data.length(); } // OH LA LA - GO PARALLEL!!! int streamParallelMethod(final String data, IntPredicate predicate) { if (data.chars().parallel().anyMatch(predicate)) { doThrow(); } return data.length(); } // Re-fill a resuable char[] with the contents // of the String's char[] int reuseBuffMethod(final char[] reusable, final String data) { final int len = data.length(); data.getChars(0, len, reusable, 0); for (int i = 0; i < len; i++) { if (reusable[i] <= ' ') { doThrow(); } } return len; } // Obtain a new copy of char[] from String int newMethod1(final String data) { final int len = data.length(); final char[] copy = data.toCharArray(); for (int i = 0; i < len; i++) { if (copy[i] <= ' ') { doThrow(); } } return len; } // Obtain a new copy of char[] from String // but use FOR-EACH int newMethod2(final String data) { for (final char c : data.toCharArray()) { if (c <= ' ') { doThrow(); } } return data.length(); } // FANCY! // OBTAIN FIELD FOR ACCESS TO THE STRING'S // INTERNAL CHAR[] int fieldMethod1(final Field field, final String data) { try { final char[] chars = (char[]) field.get(data); final int len = chars.length; for (int i = 0; i < len; i++) { if (chars[i] <= ' ') { doThrow(); } } return len; } catch (Exception ex) { throw new RuntimeException(ex); } } // same as above but use FOR-EACH int fieldMethod2(final Field field, final String data) { final char[] chars; try { chars = (char[]) field.get(data); } catch (Exception ex) { throw new RuntimeException(ex); } for (final char c : chars) { if (c <= ' ') { doThrow(); } } return chars.length; } /** * * Make a list of tests. We will shuffle a copy of this list repeatedly * while we repeat this test. * * @param data * @return */ List makeTests(String data) throws Exception { // make a list of tests final List tests = new ArrayList(); tests.add(new Jobber("charAt1") { int check() { return charAtMethod1(data); } }); tests.add(new Jobber("charAt2") { int check() { return charAtMethod2(data); } }); tests.add(new Jobber("stream") { final IntPredicate predicate = new IntPredicate() { public boolean test(int value) { return value < = ' '; } }; int check() { return streamMethod(data, predicate); } }); tests.add(new Jobber("streamPar") { final IntPredicate predicate = new IntPredicate() { public boolean test(int value) { return value <= ' '; } }; int check() { return streamParallelMethod(data, predicate); } }); // Reusable char[] method tests.add(new Jobber("reuse") { final char[] cbuff = new char[MAX_STRING_SIZE]; int check() { return reuseBuffMethod(cbuff, data); } }); // New char[] from String tests.add(new Jobber("new1") { int check() { return newMethod1(data); } }); // New char[] from String tests.add(new Jobber("new2") { int check() { return newMethod2(data); } }); // Use reflection for field access tests.add(new Jobber("field1") { final Field field; { field = String.class.getDeclaredField("value"); field.setAccessible(true); } int check() { return fieldMethod1(field, data); } }); // Use reflection for field access tests.add(new Jobber("field2") { final Field field; { field = String.class.getDeclaredField("value"); field.setAccessible(true); } int check() { return fieldMethod2(field, data); } }); return tests; } /** * We use this class to keep track of test results */ abstract class Jobber { final String name; long nanos; long chars; long runs; Jobber(String name) { this.name = name; } abstract int check(); final double nanosPerChar() { double charsPerRun = chars / runs; long nanosPerRun = nanos / runs; return charsPerRun == 0 ? nanosPerRun : nanosPerRun / charsPerRun; } final void run() { runs++; long time = System.nanoTime(); chars += check(); nanos += System.nanoTime() - time; } } // MAKE A TEST STRING OF RANDOM CHARACTERS AZ private String makeTestString(int testSize, char start, char end) { Random r = new Random(); char[] data = new char[testSize]; for (int i = 0; i < data.length; i++) { data[i] = (char) (start + r.nextInt(end)); } return new String(data); } // WE DO THIS IF WE FIND AN ILLEGAL CHARACTER IN THE STRING public void doThrow() { throw new RuntimeException("Bzzzt -- Illegal Character!!"); } /** * 1. get random string of correct length 2. get tests (List) 3. * perform tests repeatedly, shuffling each time */ List test(int size, int tries, Random random) throws Exception { String data = makeTestString(size, 'A', 'Z'); List tests = makeTests(data); List copy = new ArrayList<>(tests); while (tries-- > 0) { Collections.shuffle(copy, random); for (Jobber ti : copy) { ti.run(); } } // check to make sure all char counts the same long runs = tests.get(0).runs; long count = tests.get(0).chars; for (Jobber ti : tests) { if (ti.runs != runs && ti.chars != count) { throw new Exception("Char counts should match if all correct algorithms"); } } return tests; } private void printHeadings(final int TRIES_PER_STRING_SIZE, final Random random) throws Exception { System.out.print(" Size"); for (Jobber ti : test(0, TRIES_PER_STRING_SIZE, random)) { System.out.printf("%9s", ti.name); } System.out.println(""); } private void reportResults(int size, List tests) { System.out.printf("%6d", size); for (Jobber ti : tests) { System.out.printf("%,9.2f", ti.nanosPerChar()); } System.out.println(""); } } 

Isso é apenas micro-otimização com a qual você não deveria se preocupar.

 char[] chars = str.toCharArray(); 

retorna uma cópia dos arrays de caracteres str (no JDK, ele retorna uma cópia dos caracteres chamando System.arrayCopy ).

Fora isso, str.charAt() somente verifica se o índice está realmente dentro dos limites e retorna um caractere dentro do índice da matriz.

O primeiro não cria memory adicional na JVM.

Apenas por curiosidade e para comparar com a resposta de Saint Hill.

Se você precisar processar dados pesados, não use a JVM no modo cliente. O modo cliente não é feito para otimizações.

Vamos comparar os resultados dos benchmarks @Saint Hill usando uma JVM no modo Client e no modo Server.

 Core2Quad Q6600 G0 @ 2.4GHz JavaSE 1.7.0_40 

Veja também: Diferenças reais entre “java -server” e “java -client”?


MODO CLIENTE:

 len = 2: 111k charAt(i), 105k cbuff[i], 62k new[i], 17k field access. (chars/ms) len = 4: 285k charAt(i), 166k cbuff[i], 114k new[i], 43k field access. (chars/ms) len = 6: 315k charAt(i), 230k cbuff[i], 162k new[i], 69k field access. (chars/ms) len = 8: 333k charAt(i), 275k cbuff[i], 181k new[i], 85k field access. (chars/ms) len = 12: 342k charAt(i), 342k cbuff[i], 222k new[i], 117k field access. (chars/ms) len = 16: 363k charAt(i), 347k cbuff[i], 275k new[i], 152k field access. (chars/ms) len = 20: 363k charAt(i), 392k cbuff[i], 289k new[i], 180k field access. (chars/ms) len = 24: 375k charAt(i), 428k cbuff[i], 311k new[i], 205k field access. (chars/ms) len = 28: 378k charAt(i), 474k cbuff[i], 341k new[i], 233k field access. (chars/ms) len = 32: 376k charAt(i), 492k cbuff[i], 340k new[i], 251k field access. (chars/ms) len = 64: 374k charAt(i), 551k cbuff[i], 374k new[i], 367k field access. (chars/ms) len = 128: 385k charAt(i), 624k cbuff[i], 415k new[i], 509k field access. (chars/ms) len = 256: 390k charAt(i), 675k cbuff[i], 436k new[i], 619k field access. (chars/ms) len = 512: 394k charAt(i), 703k cbuff[i], 439k new[i], 695k field access. (chars/ms) len = 1024: 395k charAt(i), 718k cbuff[i], 462k new[i], 742k field access. (chars/ms) len = 2048: 396k charAt(i), 725k cbuff[i], 471k new[i], 767k field access. (chars/ms) len = 4096: 396k charAt(i), 727k cbuff[i], 459k new[i], 780k field access. (chars/ms) len = 8192: 397k charAt(i), 712k cbuff[i], 446k new[i], 772k field access. (chars/ms) 

MODO DE SERVIDOR:

 len = 2: 86k charAt(i), 41k cbuff[i], 46k new[i], 80k field access. (chars/ms) len = 4: 571k charAt(i), 250k cbuff[i], 97k new[i], 222k field access. (chars/ms) len = 6: 666k charAt(i), 333k cbuff[i], 125k new[i], 315k field access. (chars/ms) len = 8: 800k charAt(i), 400k cbuff[i], 181k new[i], 380k field access. (chars/ms) len = 12: 800k charAt(i), 521k cbuff[i], 260k new[i], 545k field access. (chars/ms) len = 16: 800k charAt(i), 592k cbuff[i], 296k new[i], 640k field access. (chars/ms) len = 20: 800k charAt(i), 666k cbuff[i], 408k new[i], 800k field access. (chars/ms) len = 24: 800k charAt(i), 705k cbuff[i], 452k new[i], 800k field access. (chars/ms) len = 28: 777k charAt(i), 736k cbuff[i], 368k new[i], 933k field access. (chars/ms) len = 32: 800k charAt(i), 780k cbuff[i], 571k new[i], 969k field access. (chars/ms) len = 64: 800k charAt(i), 901k cbuff[i], 800k new[i], 1306k field access. (chars/ms) len = 128: 1084k charAt(i), 888k cbuff[i], 633k new[i], 1620k field access. (chars/ms) len = 256: 1122k charAt(i), 966k cbuff[i], 729k new[i], 1790k field access. (chars/ms) len = 512: 1163k charAt(i), 1007k cbuff[i], 676k new[i], 1910k field access. (chars/ms) len = 1024: 1179k charAt(i), 1027k cbuff[i], 698k new[i], 1954k field access. (chars/ms) len = 2048: 1184k charAt(i), 1043k cbuff[i], 732k new[i], 2007k field access. (chars/ms) len = 4096: 1188k charAt(i), 1049k cbuff[i], 742k new[i], 2031k field access. (chars/ms) len = 8192: 1157k charAt(i), 1032k cbuff[i], 723k new[i], 2048k field access. (chars/ms) 

CONCLUSÃO:

Como você pode ver, o modo do servidor é muito mais rápido.

O primeiro usando str.charAt deve ser mais rápido.

Se você cavar dentro do código-fonte da class String , podemos ver que o charAt é implementado da seguinte forma:

 public char charAt(int index) { if ((index < 0) || (index >= count)) { throw new StringIndexOutOfBoundsException(index); } return value[index + offset]; } 

Aqui, tudo o que faz é indexar uma matriz e retornar o valor.

Agora, se vemos a implementação de toCharArray , vamos encontrar o abaixo:

 public char[] toCharArray() { char result[] = new char[count]; getChars(0, count, result, 0); return result; } public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > count) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, offset + srcBegin, dst, dstBegin, srcEnd - srcBegin); } 

Como você vê, está fazendo um System.arraycopy que definitivamente vai ser um pouco mais lento do que não fazê-lo.

Parece que mais é mais rápido ou mais lento

  public static void main(String arguments[]) { //Build a long string StringBuilder sb = new StringBuilder(); for(int j = 0; j < 10000; j++) { sb.append("a really, really long string"); } String str = sb.toString(); for (int testscount = 0; testscount < 10; testscount ++) { //Test 1 long start = System.currentTimeMillis(); for(int c = 0; c < 10000000; c++) { for (int i = 0, n = str.length(); i < n; i++) { char chr = str.charAt(i); doSomethingWithChar(chr);//To trick JIT optimistaion } } System.out.println("1: " + (System.currentTimeMillis() - start)); //Test 2 start = System.currentTimeMillis(); char[] chars = str.toCharArray(); for(int c = 0; c < 10000000; c++) { for (int i = 0, n = chars.length; i < n; i++) { char chr = chars[i]; doSomethingWithChar(chr);//To trick JIT optimistaion } } System.out.println("2: " + (System.currentTimeMillis() - start)); System.out.println(); } } public static void doSomethingWithChar(char chr) { int newInt = chr << 2; } 

Para as cordas longas, escolho a primeira. Por que copiar em longas cordas? Documentações diz:

public char [] toCharArray () Converte essa string em uma nova matriz de caracteres.

Retorna: uma matriz de caracteres recém-alocada cujo tamanho é o comprimento dessa string e cujo conteúdo é inicializado para conter a sequência de caracteres representada por essa string.

// Editar 1

Eu mudei o teste para enganar a otimização JIT.

// Editar 2

Repita o teste 10 vezes para deixar o JVM aquecer.

// Editar 3

Conclusões

Primeiro de tudo str.toCharArray(); copia toda a cadeia na memory. Pode consumir memory para longas seqüências de caracteres. O método String.charAt( ) procura o char na matriz char dentro da class String, verificando o índice antes. Parece que o primeiro método Strings primeiro (isto chatAt método chatAt ) é um pouco mais lento devido a esta verificação de índice. Mas se o String for longo o suficiente, copiar todo o array char fica mais lento, e o primeiro método é mais rápido. Quanto mais longa for a string, mais lento toCharArray desempenho de toCharArray . Tente alterar o limite for(int j = 0; j < 10000; j++) loop for(int j = 0; j < 10000; j++) para vê-lo. Se deixarmos o JVM aquecer, o código será executado mais rapidamente, mas as proporções serão as mesmas.

Afinal, é apenas micro-otimização.

Apesar da resposta de @Saint Hill, se você considerar a complexidade de tempo de str.toCharArray () ,

o primeiro é mais rápido até mesmo para strings muito grandes. Você pode executar o código abaixo para ver por si mesmo.

  char [] ch = new char[1_000_000_00]; String str = new String(ch); // to create a large string // ---> from here long currentTime = System.nanoTime(); for (int i = 0, n = str.length(); i < n; i++) { char c = str.charAt(i); } // ---> to here System.out.println("str.charAt(i):"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)"); /** * ch = str.toCharArray() itself takes lots of time */ // ---> from here currentTime = System.nanoTime(); ch = str.toCharArray(); for (int i = 0, n = str.length(); i < n; i++) { char c = ch[i]; } // ---> to here System.out.println("ch = str.toCharArray() + c = ch[i] :"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)"); 

saída:

 str.charAt(i):5.492102 (ms) ch = str.toCharArray() + c = ch[i] :79.400064 (ms) 

String.toCharArray() cria uma nova matriz char, significa a alocação de memory do tamanho da string, depois copia a matriz char original da string usando System.arraycopy() e retorna esta cópia ao chamador. String.charAt () retorna o caractere na posição i da cópia original, é por isso que String.charAt() será mais rápido que String.toCharArray() . Embora, String.toCharArray() retorna cópia e não char da matriz String original, onde String.charAt() retorna o caractere da matriz char original. O código abaixo retorna o valor no índice especificado dessa string.

 public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; } 

código abaixo retorna uma matriz de caracteres recém-alocada cujo comprimento é o comprimento dessa string

 public char[] toCharArray() { // Cannot use Arrays.copyOf because of class initialization order issues char result[] = new char[value.length]; System.arraycopy(value, 0, result, 0, value.length); return result; } 

O segundo faz com que uma nova matriz char seja criada, e todos os caracteres da String sejam copiados para essa nova matriz char, então eu diria que a primeira é mais rápida (e menos faminta por memory).