Was wäre in Java der schnellste Weg, um alle Zeichen in einem String zu durchlaufen:
String str = "a really, really long string";
for (int i = 0, n = str.length(); i < n; i++) {
char c = str.charAt(i);
}
Oder dieses:
char[] chars = str.toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
char c = chars[i];
}
EDIT:
Ich möchte wissen, ob die Kosten für das wiederholte Aufrufen der charAt
Methode während einer langen Iteration geringer oder höher sind als die Kosten für das Ausführen eines einzelnen Aufrufs zu toCharArray
Beginn und den direkten Zugriff auf das Array während der Iteration.
Es wäre großartig, wenn jemand einen robusten Benchmark für verschiedene Saitenlängen liefern könnte, unter Berücksichtigung der JIT-Aufwärmzeit, der JVM-Startzeit usw. und nicht nur des Unterschieds zwischen zwei Aufrufen von System.currentTimeMillis()
.
for (char c : chars)
?charAt
entweder kleiner oder höher sind als die Kosten für einen einzelnen Anruf beitoCharArray
Antworten:
ERSTES UPDATE: Bevor Sie dies in einer Produktionsumgebung versuchen (nicht empfohlen), lesen Sie zuerst Folgendes : http://www.javaspecialists.eu/archive/Issue237.html Ab Java 9 funktioniert die beschriebene Lösung nicht mehr , da Java jetzt Zeichenfolgen standardmäßig als Byte [] speichert.
ZWEITES UPDATE: Ab dem 25.10.2016 gibt es auf meinem AMDx64 8core und Source 1.8 keinen Unterschied zwischen der Verwendung von 'charAt' und dem Feldzugriff. Es scheint, dass das JVM ausreichend optimiert ist, um alle Aufrufe von 'string.charAt (n)' zu inline und zu rationalisieren.
Es hängt alles von der Länge der
String
Inspektion ab. Wenn es sich, wie die Frage sagt, um lange Zeichenfolgen handelt, besteht die schnellste Möglichkeit, die Zeichenfolge zu überprüfen, darin, mithilfe der Reflexion auf die Rückseitechar[]
der Zeichenfolge zuzugreifen .Ein vollständig randomisierter Benchmark mit JDK 8 (win32 und win64) auf einem 64 AMD Phenom II 4 Core 955 mit 3,2 GHz (sowohl im Client- als auch im Servermodus) mit 9 verschiedenen Techniken (siehe unten!) Zeigt, dass die Verwendung
String.charAt(n)
für kleine Unternehmen am schnellsten ist Zeichenfolgen und die Verwendungreflection
für den Zugriff auf das String-Backing-Array ist bei großen Zeichenfolgen fast doppelt so schnell.DAS EXPERIMENT
Es werden 9 verschiedene Optimierungstechniken ausprobiert.
Alle Zeichenfolgeninhalte sind zufällig angeordnet
Der Test wird für Saitengrößen in Vielfachen von zwei durchgeführt, beginnend mit 0,1,2,4,8,16 usw.
Die Tests werden 1.000 Mal pro Zeichenfolgengröße durchgeführt
Die Tests werden jedes Mal in zufälliger Reihenfolge gemischt. Mit anderen Worten, die Tests werden jedes Mal in zufälliger Reihenfolge durchgeführt, mehr als 1000 Mal.
Die gesamte Testsuite wird vorwärts und rückwärts ausgeführt, um die Auswirkung der JVM-Aufwärmphase auf Optimierung und Zeiten zu zeigen.
Die gesamte Suite wird zweimal ausgeführt, einmal im
-client
Modus und einmal im-server
Modus.SCHLUSSFOLGERUNGEN
-Client-Modus (32 Bit)
Bei Zeichenfolgen mit einer Länge von 1 bis 256 Zeichen
string.charAt(i)
gewinnt der Aufruf mit einer durchschnittlichen Verarbeitung von 13,4 bis 588 Millionen Zeichen pro Sekunde.Außerdem ist es insgesamt 5,5% schneller (Client) und 13,9% (Server) wie folgt:
als so mit einer lokalen endgültigen Längenvariablen:
Bei langen Zeichenfolgen mit einer Länge von 512 bis 256 KB ist die Verwendung der Reflexion für den Zugriff auf das Hintergrundarray der Zeichenfolge am schnellsten. Diese Technik ist fast doppelt so schnell wie String.charAt (i) (178% schneller). Die durchschnittliche Geschwindigkeit in diesem Bereich betrug 1,111 Milliarden Zeichen pro Sekunde.
Das Feld muss im Voraus abgerufen werden und kann dann in der Bibliothek für verschiedene Zeichenfolgen wiederverwendet werden. Interessanterweise ist es im Gegensatz zum obigen Code beim Feldzugriff 9% schneller, eine lokale endgültige Längenvariable zu haben, als 'chars.length' bei der Schleifenprüfung zu verwenden. So kann der Feldzugriff am schnellsten eingerichtet werden:
Besondere Kommentare zum Server-Modus
Der Feldzugriff beginnt nach 32 Zeichenfolgen im Servermodus auf einem 64-Bit-Java-Computer auf meinem AMD 64-Computer zu gewinnen. Dies wurde erst mit einer Länge von 512 Zeichen im Client-Modus festgestellt.
Erwähnenswert ist auch, dass ich beim Ausführen von JDK 8 (32-Bit-Build) im Servermodus die Gesamtleistung sowohl für große als auch für kleine Zeichenfolgen um 7% langsamer war. Dies war mit Build 121 Dec 2013 von JDK 8 Early Release. Derzeit scheint der 32-Bit-Servermodus langsamer zu sein als der 32-Bit-Client-Modus.
Davon abgesehen ... scheint der einzige Servermodus, der es wert ist, aufgerufen zu werden, auf einem 64-Bit-Computer zu sein. Andernfalls wird die Leistung tatsächlich beeinträchtigt.
Für 32-Bit-Builds
-server mode
auf einem AMD64 kann ich Folgendes sagen:Ebenfalls erwähnenswert ist, dass String.chars () (Stream und die parallele Version) eine Pleite sind. Viel langsamer als jeder andere Weg. Die
Streams
API ist eine ziemlich langsame Methode, um allgemeine Zeichenfolgenoperationen auszuführen.Wunschzettel
Java String kann Prädikate haben, die optimierte Methoden akzeptieren, wie z. B. enthält (Prädikat), forEach (Consumer), forEachWithIndex (Consumer). Ohne dass der Benutzer die Länge kennen oder Aufrufe von String-Methoden wiederholen muss, können diese das Parsen von Bibliotheken unterstützen
beep-beep beep
beschleunigen.Träum weiter :)
Happy Strings!
~ SH
Der Test verwendete die folgenden 9 Methoden zum Testen der Zeichenfolge auf das Vorhandensein von Leerzeichen:
"charAt1" - PRÜFEN SIE DEN STRING-INHALT AUF DIE GEWÖHNLICHE ART:
"charAt2" - GLEICH WIE OBEN, ABER VERWENDEN SIE String.length (), STATT EINE ENDGÜLTIGE LOKALE int FÜR DIE LÄNGE ZU MACHEN
"stream" - VERWENDEN Sie den IntStream des NEUEN JAVA-8-Strings und geben Sie ihm ein Prädikat für die Überprüfung
"streamPara" - GLEICH WIE OBEN, ABER OH-LA-LA - GO PARALLEL !!!
"wiederverwenden" - FÜLLEN SIE EIN WIEDERVERWENDBARES Zeichen [] MIT DEN INHALTEN DER STRINGS NACH
"new1" - ERHALTEN SIE EINE NEUE KOPIE DES char [] AUS DER STRING
"new2" - GLEICH WIE OBEN, ABER "FÜR JEDEN"
"field1" - FANTASTISCH !! ERHALTEN SIE EIN FELD FÜR DEN ZUGRIFF AUF DAS INTERNE Zeichen der STRING []
"field2" - GLEICH WIE OBEN, ABER "FOR-EACH"
VERBUNDENE ERGEBNISSE FÜR DEN KUNDENMODUS
-client
(Vorwärts- und Rückwärts-Tests kombiniert)Hinweis: Der -client-Modus mit Java 32-Bit und der -server-Modus mit Java 64-Bit sind auf meinem AMD64-Computer dieselben wie unten.
ZUSAMMENGESETZTE ERGEBNISSE FÜR DEN SERVERMODUS
-server
(Vorwärts- und Rückwärts-Tests kombiniert)Hinweis: Dies ist der Test für Java 32 Bit, das im Servermodus auf einem AMD64 ausgeführt wird. Der Servermodus für Java 64-Bit war der gleiche wie für Java 32-Bit im Client-Modus, außer dass der Feldzugriff nach 32 Zeichen beginnt.
VOLLSTÄNDIGER LAUFBARER PROGRAMMCODE
(Um auf Java 7 und früher zu testen, entfernen Sie die beiden Streams-Tests.)
quelle
Dies ist nur eine Mikrooptimierung, über die Sie sich keine Sorgen machen sollten.
Gibt Ihnen eine Kopie der
str
Zeichenarrays zurück (in JDK wird durch Aufrufen eine Kopie der Zeichen zurückgegebenSystem.arrayCopy
).Davon abgesehen,
str.charAt()
nur geprüft, ob der Index tatsächlich begrenzt ist, und es wird ein Zeichen innerhalb des Array-Index zurückgegeben.Der erste erstellt keinen zusätzlichen Speicher in JVM.
quelle
Nur aus Neugier und zum Vergleich mit Saint Hills Antwort.
Wenn Sie schwere Daten verarbeiten müssen, sollten Sie JVM nicht im Client-Modus verwenden. Der Client-Modus ist nicht für Optimierungen vorgesehen.
Vergleichen wir die Ergebnisse von @ Saint Hill-Benchmarks mit einer JVM im Client- und Servermodus.
Siehe auch: Echte Unterschiede zwischen "Java-Server" und "Java-Client"?
KUNDENMODUS:
SERVER-MODUS:
FAZIT:
Wie Sie sehen können, ist der Servermodus viel schneller.
quelle
Die erste Verwendung
str.charAt
sollte schneller sein.Wenn Sie in den Quellcode der
String
Klasse graben , können wir sehen, dass diescharAt
wie folgt implementiert ist:Hier wird lediglich ein Array indiziert und der Wert zurückgegeben.
Wenn wir nun die Implementierung von sehen
toCharArray
, finden wir Folgendes:Wie Sie sehen, macht es ein,
System.arraycopy
was definitiv ein bisschen langsamer sein wird, als es nicht zu tun.quelle
Trotz der Antwort von @Saint Hill, wenn Sie die zeitliche Komplexität von str.toCharArray () berücksichtigen ,
Der erste ist sogar für sehr große Saiten schneller. Sie können den folgenden Code ausführen, um sich selbst davon zu überzeugen.
Ausgabe:
quelle
Sieht so aus, als wäre nichts schneller oder langsamer
Für lange Saiten werde ich die erste wählen. Warum um lange Saiten kopieren? Dokumentationen sagt:
// Bearbeiten 1
Ich habe den Test geändert, um die JIT-Optimierung auszutricksen.
// Bearbeiten 2
Wiederholen Sie den Test 10 Mal, damit sich JVM aufwärmen kann.
// Bearbeiten 3
Schlussfolgerungen:
Zunächst einmal
str.toCharArray();
gesamte Zeichenfolge im Speicher kopiert. Es kann für lange Zeichenfolgen speicherintensiv sein. Die MethodeString.charAt( )
sucht zuvor nach char im char-Array im String-Klassenprüfungsindex. Es sieht so aus, als ob die erste Strings-Methode (dh diechatAt
Methode) aufgrund dieser Indexprüfung etwas langsamer ist. Wenn der String jedoch lang genug ist, wird das Kopieren des gesamten char-Arrays langsamer und die erste Methode ist schneller. Je länger die Zeichenfolge ist, desto langsamer wirdtoCharArray
die Leistung. Versuchen Sie, das Limit in derfor(int j = 0; j < 10000; j++)
Schleife zu ändern, um es zu sehen. Wenn wir JVM aufwärmen lassen, läuft der Code schneller, aber die Proportionen sind gleich.Immerhin ist es nur Mikrooptimierung.
quelle
for:in
Option ausprobieren , nur zum Spaß?Iterable
noch Array.String.toCharArray()
Erstellt ein neues Zeichenarray, bedeutet die Zuweisung von Speicher mit Zeichenfolgenlänge, kopiert dann das ursprüngliche Zeichenarray der Zeichenfolge mitSystem.arraycopy()
und gibt diese Kopie an den Aufrufer zurück. String.charAt () gibt das Zeichen an der Positioni
der Originalkopie zurück. DeshalbString.charAt()
ist es schneller alsString.toCharArray()
. Obwohl,String.toCharArray()
kehrt kopieren und nicht char aus Original - String - Array, woString.charAt()
kehrt Zeichen von den ursprünglichen char - Array. Der folgende Code gibt den Wert am angegebenen Index dieser Zeichenfolge zurück.Der folgende Code gibt ein neu zugewiesenes Zeichenarray zurück, dessen Länge der Länge dieser Zeichenfolge entspricht
quelle
Das zweite bewirkt, dass ein neues Zeichenarray erstellt und alle Zeichen aus dem String in dieses neue Zeichenarray kopiert werden. Ich würde also vermuten, dass das erste schneller (und weniger speicherhungrig) ist.
quelle