Il modo più veloce per iterare su tutti i caratteri in una stringa

In Java, quale sarebbe il modo più veloce per scorrere tutti i caratteri in una stringa, questo:

String str = "a really, really long string"; for (int i = 0, n = str.length(); i < n; i++) { char c = str.charAt(i); } 

O questo:

 char[] chars = str.toCharArray(); for (int i = 0, n = chars.length; i < n; i++) { char c = chars[i]; } 

MODIFICARE :

Quello che mi piacerebbe sapere è se il costo di chiamare ripetutamente il metodo charAt durante una lunga iterazione finisce per essere inferiore o superiore al costo di eseguire una singola chiamata a toCharArray all’inizio e quindi accedere direttamente alla matrice durante iterazione.

Sarebbe bello se qualcuno potesse fornire un solido punto di riferimento per lunghezze di stringa diverse, avendo in mente il tempo di riscaldamento JIT, l’ora di avvio JVM, ecc. E non solo la differenza tra due chiamate a System.currentTimeMillis() .

PRIMO AGGIORNAMENTO: prima di provare questo in un ambiente di produzione (sconsigliato), leggi prima questo: http://www.javaspecialists.eu/archive/Issue237.html A partire da Java 9, la soluzione come descritta non funzionerà più , perché ora Java memorizzerà le stringhe come byte [] per impostazione predefinita.

SECONDO AGGIORNAMENTO: A partire dal 2016-10-25, sul mio AMDx64 8core e sul sorgente 1.8, non vi è alcuna differenza tra l’uso di “charAt” e l’accesso al campo. Sembra che jvm sia sufficientemente ottimizzato per incorporare e semplificare qualsiasi chiamata ‘stringa.charAt (n)’.

Tutto dipende dalla lunghezza della String da ispezionare. Se, come dice la domanda, è per le stringhe lunghe , il modo più veloce per ispezionare la stringa è usare la reflection per accedere al char[] backing char[] della stringa.

Un benchmark completamente randomizzato con JDK 8 (win32 e win64) su un 64 core AMD Phenom II 4 955 @ 3.2 GHZ (sia in modalità client che in modalità server) con 9 diverse tecniche (vedi sotto!) Mostra che l’uso di String.charAt(n) è il più veloce per le stringhe piccole e che l’utilizzo della reflection per accedere all’array di backing delle stringhe è quasi due volte più veloce per le stringhe di grandi dimensioni.

L’ESPERIMENTO

  • Vengono provate 9 diverse tecniche di ottimizzazione.

  • Tutti i contenuti delle stringhe sono randomizzati

  • Il test è fatto per le dimensioni delle stringhe in multipli di due a partire da 0,1,2,4,8,16 ecc.

  • I test vengono eseguiti 1.000 volte per dimensione della stringa

  • I test vengono mescolati in ordine casuale ogni volta. In altre parole, i test vengono eseguiti in ordine casuale ogni volta che vengono eseguiti, oltre 1000 volte.

  • L’intera suite di test viene eseguita avanti e indietro, per mostrare l’effetto del riscaldamento JVM sull’ottimizzazione e sui tempi.

  • L’intera suite viene eseguita due volte, una volta in modalità -client e l’altra in modalità -server .

CONCLUSIONI

-client mode (32 bit)

Per stringhe da 1 a 256 caratteri , la chiamata a string.charAt(i) vince con un’elaborazione media da 13,4 milioni a 588 milioni di caratteri al secondo.

Inoltre, è complessivamente del 5,5% più veloce (client) e del 13,9% (server) come questo:

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

piuttosto che con una variabile di lunghezza finale locale:

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

Per le stringhe lunghe, lunghezza da 512 a 256K caratteri , utilizzando la riflessione per accedere alla matrice di supporto della stringa è più veloce. Questa tecnica è quasi due volte più veloce di String.charAt (i) (178% più veloce). La velocità media su questo intervallo era di 1.111 miliardi di caratteri al secondo.

Il campo deve essere ottenuto prima del tempo e quindi può essere riutilizzato nella libreria su stringhe diverse. È interessante notare che, a differenza del codice sopra, con l'accesso Field, è 9% più veloce ad avere una variabile di lunghezza finale locale che usare 'chars.length' nel controllo del ciclo. Ecco come l'accesso al campo può essere configurato più velocemente:

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

Commenti speciali sulla modalità -server

Accesso al campo che inizia vincendo dopo 32 stringhe di lunghezza carattere in modalità server su una macchina Java a 64 bit sulla mia macchina AMD 64. Non è stato visto fino a 512 caratteri in modalità client.

Vale anche la pena notare che, quando eseguivo JDK 8 (32 bit build) in modalità server, le prestazioni complessive erano del 7% più lente per stringhe grandi e piccole. Questo è stato con build 121 dic 2013 di JDK 8 early release. Quindi, per ora, sembra che la modalità server a 32 bit sia più lenta della modalità client a 32 bit.

Detto questo ... sembra che l'unica modalità server che vale la pena invocare sia su una macchina a 64 bit. Altrimenti ostacola effettivamente le prestazioni.

Per la compilazione a 32 bit in esecuzione in -server mode su un AMD64, posso dire questo:

  1. String.charAt (i) è il chiaro vincitore nel complesso. Sebbene tra le dimensioni da 8 a 512 caratteri ci fossero vincitori tra "nuovo" "riuso" e "campo".
  2. String.charAt (i) è il 45% più veloce in modalità client
  3. L'accesso al campo è due volte più veloce per le stringhe di grandi dimensioni in modalità client.

Vale anche la pena di dire, String.chars () (Stream e la versione parallela) sono un busto. Molto più lento di qualsiasi altro modo. L'API Streams è un modo piuttosto lento per eseguire operazioni generali sulle stringhe.

Lista dei desideri

Java String potrebbe avere predicato che accetta metodi ottimizzati come contiene (predicato), per Ogni utente (consumer), perEveryWithIndex (consumer). Pertanto, senza la necessità per l'utente di conoscere la lunghezza o ripetere le chiamate ai metodi String, questi potrebbero aiutare l'analisi delle librerie beep-beep beep speedup.

Continua a sognare 🙂

Corde felici!

~ SH

Il test ha utilizzato i seguenti 9 metodi per testare la stringa per la presenza di spazi bianchi:

"charAt1" - CONTROLLA LA STRINGA SULLA MODALITÀ UTENTE:

 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" - SAME AS ABOVE BUT USE String.length () INVECE DI REALIZZARE UN FINAL LOCAL int PER LA LUNGHEZZA

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

"stream" - USARE IL NUOVO INTERStream String della JAVA-8 E PASSARE UN PREDICATO PER FARE IL CONTROLLO

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

"streamPara" - SOPRA, SOPRA OH-LA-LA - VAI PARALLELO !!!

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

"riutilizzare" - RICARICA UN CARATTERE RIUTILIZZABILE [] CON I CONTENUTI DELLE STRINGHE

 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": OTTENERE UNA NUOVA COPIA DEL carattere [] DALLA STRINGA

 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": SOPRA, MA UTILIZZA "PER-CIASCUNO"

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

"field1" - FANCY !! OTTENERE IL CAMPO PER ACCEDERE AL CARATTERE INTERNO DELLA STRINGA []

 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": SOPRA, MA UTILIZZA "PER-CIASCUNO"

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

RISULTATI COMPOSITI PER MODALITÀ -client CLIENT (test avanti e indietro combinati)

Nota: che la modalità -client con Java a 32 bit e la modalità server con Java 64 bit sono le stesse di seguito sulla mia macchina 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 

RISULTATI COMPOSITI PER SERVER -server MODE (test avanti e indietro combinati)

Nota: questo è il test per Java 32 bit in esecuzione in modalità server su un AMD64. La modalità server per Java a 64 bit era la stessa di Java a 32 bit in modalità client, con la differenza che l'accesso al campo iniziava vincendo dopo 32 caratteri.

 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 

CODICE PROGRAMMA COMPLETO RUNNABLE

(per testare su Java 7 e precedenti, rimuovere i due test di streaming)

 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(""); } } 

Questa è solo una micro-ottimizzazione di cui non dovresti preoccuparti.

 char[] chars = str.toCharArray(); 

restituisce una copia degli array di caratteri str (in JDK restituisce una copia di caratteri chiamando System.arrayCopy ).

Oltre a questo, str.charAt() controlla solo se l’indice è effettivamente in limiti e restituisce un carattere all’interno dell’indice dell’array.

Il primo non crea memoria aggiuntiva in JVM.

Solo per curiosità e per confrontarsi con la risposta di Saint Hill.

Se è necessario elaborare dati pesanti, non utilizzare la JVM in modalità client. La modalità client non è fatta per le ottimizzazioni.

Confrontiamo i risultati dei benchmark di @Saint Hill utilizzando una JVM in modalità Client e Server.

 Core2Quad Q6600 G0 @ 2.4GHz JavaSE 1.7.0_40 

Vedi anche: Vere differenze tra “java -server” e “java-client”?


MODALITÀ 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) 

MODALITÀ SERVER:

 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) 

CONCLUSIONE:

Come puoi vedere, la modalità server è molto più veloce.

Il primo che usa str.charAt dovrebbe essere più veloce.

Se si scava all’interno del codice sorgente della class String , possiamo vedere che charAt è implementato come segue:

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

Qui, tutto ciò che fa è indicizzare una matrice e restituire il valore.

Ora, se vediamo l’implementazione di toCharArray , troveremo il seguente:

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

Come vedi, sta facendo un System.arraycopy che sarà sicuramente un po ‘più lento del non farlo.

Sembra che niether sia più veloce o più 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; } 

Per le corde lunghe sceglierò il primo. Perché copiare lungo le stringhe? La documentazione dice:

public char [] toCharArray () Converte questa stringa in una nuova matrice di caratteri.

Restituisce: una matrice di caratteri appena allocata la cui lunghezza è la lunghezza di questa stringa e il cui contenuto è inizializzato per contenere la sequenza di caratteri rappresentata da questa stringa.

// Modifica 1

Ho modificato il test per ingannare l'ottimizzazione JIT.

// Modifica 2

Ripeti il ​​test 10 volte per far scaldare JVM.

// Modifica 3

conclusioni:

Prima di tutto str.toCharArray(); copia tutta la stringa in memoria. Può essere consumato dalla memoria per stringhe lunghe. Metodo String.charAt( ) cerca il char nell'array char all'interno dell'indice di controllo della class String prima. Sembra che il primo metodo chatAt stringhe (il metodo chatAt ) sia un po 'più lento a causa di questo controllo dell'indice. Ma se la stringa è abbastanza lunga, la copia dell'intero array di char diventa più lenta e il primo metodo è più veloce. Più lunga è la stringa, più lento è il toCharArray . Prova a cambiare il limite in for(int j = 0; j < 10000; j++) per vederlo. Se lasciamo scaldare il codice JVM più veloce, ma le proporzioni sono le stesse.

Dopotutto è solo micro-ottimizzazione.

Nonostante la risposta di @Saint Hill se si considera la complessità temporale di str.toCharArray () ,

il primo è più veloce anche per stringhe molto grandi. Puoi eseguire il codice qui sotto per vederlo da solo.

  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)"); 

produzione:

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

String.toCharArray() crea un nuovo array di caratteri, indica l’allocazione della memoria della lunghezza della stringa, quindi copia l’array di caratteri originale della stringa utilizzando System.arraycopy() e quindi restituisce questa copia al chiamante. String.charAt () restituisce il carattere alla posizione i dalla copia originale, ecco perché String.charAt() sarà più veloce di String.toCharArray() . Sebbene, String.toCharArray() restituisca la copia e non il char dall’array String originale, dove String.charAt() restituisce il carattere dall’array char originale. Il codice sottostante restituisce il valore sull’indice specificato di questa stringa.

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

il codice sottostante restituisce un array di caratteri appena assegnato la cui lunghezza è la lunghezza di questa stringa

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

La seconda causa la creazione di un nuovo array di caratteri e tutti i caratteri della stringa da copiare in questo nuovo array di caratteri, quindi suppongo che il primo sia più veloce (e meno affamato di memoria).