Was ist der schnellste Weg, um alle nicht druckbaren Zeichen von einem String
in Java zu entfernen?
Bisher habe ich versucht, einen 138-Byte-String mit 131 Zeichen zu messen:
- String's
replaceAll()
- langsamste Methode- 517009 Ergebnisse / Sek
- Kompilieren Sie ein Muster vor und verwenden Sie dann Matcher's
replaceAll()
- 637836 Ergebnisse / Sek
- Verwenden Sie StringBuffer, rufen Sie Codepunkte
codepointAt()
einzeln ab und hängen Sie sie an StringBuffer an- 711946 Ergebnisse / Sek
- Verwenden Sie StringBuffer, rufen Sie Zeichen einzeln ab
charAt()
und hängen Sie sie an StringBuffer an- 1052964 Ergebnisse / Sek
- Ordnen Sie einen
char[]
Puffer vorab zu , rufen Sie die Zeichen einzeln ab ,charAt()
füllen Sie diesen Puffer und konvertieren Sie ihn zurück in String- 2022653 Ergebnisse / Sek
- Ordnen Sie 2
char[]
Puffer vorab zu - alt und neu, rufen Sie alle Zeichen für den vorhandenen String auf einmal abgetChars()
, durchlaufen Sie den alten Puffer einzeln und füllen Sie den neuen Puffer, und konvertieren Sie dann den neuen Puffer in einen String - meine schnellste Version- 2502502 Ergebnisse / Sek
- Gleiches Zeug mit 2 Puffern - nur mit
byte[]
,getBytes()
und der Angabe Codierung als „UTF-8“- 857485 Ergebnisse / Sek
- Gleiches Zeug mit 2
byte[]
Puffern, aber Angabe der Codierung als KonstanteCharset.forName("utf-8")
- 791076 Ergebnisse / Sek
- Gleiches Zeug mit 2
byte[]
Puffern, aber Angabe der Codierung als lokale 1-Byte-Codierung (kaum eine vernünftige Sache)- 370164 Ergebnisse / Sek
Mein bester Versuch war der folgende:
char[] oldChars = new char[s.length()];
s.getChars(0, s.length(), oldChars, 0);
char[] newChars = new char[s.length()];
int newLen = 0;
for (int j = 0; j < s.length(); j++) {
char ch = oldChars[j];
if (ch >= ' ') {
newChars[newLen] = ch;
newLen++;
}
}
s = new String(newChars, 0, newLen);
Irgendwelche Gedanken darüber, wie man es noch schneller macht?
Bonuspunkte für die Beantwortung einer sehr seltsamen Frage: Warum die Verwendung des Zeichensatznamens "utf-8" direkt zu einer besseren Leistung führt als die Verwendung einer vorab zugewiesenen statischen Konstante Charset.forName("utf-8")
?
Aktualisieren
- Vorschlag von Ratschenfreak liefert eine beeindruckende Leistung von 3105590 Ergebnissen / Sek., Eine Verbesserung um + 24%!
- Der Vorschlag von Ed Staub liefert eine weitere Verbesserung - 3471017 Ergebnisse / Sek., Ein Plus von 12% gegenüber dem vorherigen Bestwert.
Update 2
Ich habe mein Bestes gegeben, um alle vorgeschlagenen Lösungen und ihre Kreuzmutationen zu sammeln und sie als kleines Benchmarking-Framework bei github zu veröffentlichen . Derzeit gibt es 17 Algorithmen. Einer davon ist der "spezielle" Voo1- Algorithmus ( bereitgestellt vom SO-Benutzer Voo) ) verwendet komplizierte Reflexionstricks, um hervorragende Geschwindigkeiten zu erreichen, JVM-Strings .
Sie können es gerne ausprobieren und ausführen, um die Ergebnisse auf Ihrer Box zu ermitteln. Hier ist eine Zusammenfassung meiner Ergebnisse. Es ist Spezifikationen:
- Debian Sid
- Linux 2.6.39-2-amd64 (x86_64)
- Java aus einem Paket installiert
sun-java6-jdk-6.24-1
, identifiziert sich JVM als- Java (TM) SE-Laufzeitumgebung (Build 1.6.0_24-b07)
- Java HotSpot (TM) 64-Bit-Server-VM (Build 19.1-b02, gemischter Modus)
Unterschiedliche Algorithmen zeigen letztendlich unterschiedliche Ergebnisse bei unterschiedlichen Eingabedaten. Ich habe einen Benchmark in 3 Modi durchgeführt:
Gleiche einzelne Zeichenfolge
Dieser Modus arbeitet mit derselben einzelnen Zeichenfolge, die von der StringSource
Klasse als Konstante bereitgestellt wird . Der Showdown ist:
Ops / s │ Algorithmus ─────────────────────────────────────────── 6 535 947 │ Voo1 ─────────────────────────────────────────── 5 350 454 │ RatchetFreak2EdStaub1GreyCat1 5 249 343 │ EdStaub1 5 002 501 │ EdStaub1GreyCat1 4 859 086 │ ArrayOfCharFromStringCharAt 4 295 532 │ RatchetFreak1 4 045 307 │ ArrayOfCharFromArrayOfChar 2 790 178 │ RatchetFreak2EdStaub1GreyCat2 2 583 311 │ RatchetFreak2 1 274 859 │ StringBuilderChar 1 138 174 │ StringBuilderCodePoint 994 727 │ ArrayOfByteUTF8String 918 611 │ ArrayOfByteUTF8Const 756 086 │ MatcherReplace 598 945 │ StringReplaceAll 460 045 │ ArrayOfByteWindows1251
In Diagrammform :
(Quelle: greycat.ru )
Mehrere Zeichenfolgen, 100% der Zeichenfolgen enthalten Steuerzeichen
Der Quell-String-Anbieter hat viele zufällige Strings mit dem Zeichensatz (0..127) vorgeneriert - daher enthielten fast alle Strings mindestens ein Steuerzeichen. Algorithmen haben Strings von diesem vorgenerierten Array im Round-Robin-Verfahren empfangen.
Ops / s │ Algorithmus ─────────────────────────────────────────── 2 123 142 │ Voo1 ─────────────────────────────────────────── 1 782 214 │ EdStaub1 1 776 199 │ EdStaub1GreyCat1 1 694 628 │ ArrayOfCharFromStringCharAt 1 481 481 │ ArrayOfCharFromArrayOfChar 1 460 067 │ RatchetFreak2EdStaub1GreyCat1 1 438 435 │ RatchetFreak2EdStaub1GreyCat2 1 366 494 │ RatchetFreak2 1 349 710 │ RatchetFreak1 893 176 │ ArrayOfByteUTF8String 817 127 │ ArrayOfByteUTF8Const 778 089 │ StringBuilderChar 734 754 │ StringBuilderCodePoint 377 829 │ ArrayOfByteWindows1251 224 140 │ MatcherReplace 211 104 │ StringReplaceAll
In Diagrammform :
(Quelle: greycat.ru )
Mehrere Zeichenfolgen, 1% der Zeichenfolgen enthalten Steuerzeichen
Wie zuvor, jedoch wurde nur 1% der Zeichenfolgen mit Steuerzeichen generiert - andere 99% wurden mit dem Zeichensatz [32..127] generiert, sodass sie überhaupt keine Steuerzeichen enthalten konnten. Diese synthetische Last kommt der realen Anwendung dieses Algorithmus bei mir am nächsten.
Ops / s │ Algorithmus ─────────────────────────────────────────── 3 711 952 │ Voo1 ─────────────────────────────────────────── 2 851 440 │ EdStaub1GreyCat1 2 455 796 │ EdStaub1 2 426 007 │ ArrayOfCharFromStringCharAt 2 347 969 │ RatchetFreak2EdStaub1GreyCat2 2 242 152 │ RatchetFreak1 2 171 553 │ ArrayOfCharFromArrayOfChar 1 922 707 │ RatchetFreak2EdStaub1GreyCat1 1 857 010 │ RatchetFreak2 1 023 751 │ ArrayOfByteUTF8String 939 055 │ StringBuilderChar 907 194 │ ArrayOfByteUTF8Const 841 963 │ StringBuilderCodePoint 606 465 │ MatcherReplace 501 555 │ StringReplaceAll 381 185 │ ArrayOfByteWindows1251
In Diagrammform :
(Quelle: greycat.ru )
Es ist sehr schwer für mich zu entscheiden, wer die beste Antwort geliefert hat, aber angesichts der realen Anwendung, die die beste Lösung von Ed Staub gegeben / inspiriert hat, wäre es fair, seine Antwort zu markieren. Vielen Dank für alle, die daran teilgenommen haben. Ihre Beiträge waren sehr hilfreich und von unschätzbarem Wert. Fühlen Sie sich frei, die Testsuite auf Ihrer Box auszuführen und noch bessere Lösungen vorzuschlagen (funktionierende JNI-Lösung, irgendjemand?).
Verweise
- GitHub-Repository mit einer Benchmarking-Suite
StringBuilder
wird geringfügig schneller sein alsStringBuffer
es nicht synchronisiert ist, ich erwähne dies nur, weil Sie dies markiert habenmicro-optimization
s.length()
auch aus derfor
Schleife extrahieren :-)\t
und\n
. Viele Zeichen über 127 sind in Ihrem Zeichensatz nicht druckbar.s.length()
initiiert?Antworten:
Wenn es sinnvoll ist, diese Methode in eine Klasse einzubetten, die nicht für mehrere Threads freigegeben ist, können Sie den Puffer wiederverwenden:
etc...
Dies ist ein großer Gewinn - 20% oder so, wie ich den derzeit besten Fall verstehe.
Wenn dies für potenziell große Zeichenfolgen verwendet werden soll und das "Speicherleck" ein Problem darstellt, kann eine schwache Referenz verwendet werden.
quelle
Die Verwendung eines 1-Zeichen-Arrays könnte etwas besser funktionieren
und ich vermied wiederholte Anrufe zu
s.length();
Eine andere Mikrooptimierung, die funktionieren könnte, ist
quelle
newLen++;
: Was ist mit Vorinkrement++newLen;
? - (auch++j
in der Schleife). Schauen Sie hier: stackoverflow.com/questions/1546981/…final
zu diesem Algorithmus und das VerwendenoldChars[newLen++]
(++newLen
ist ein Fehler - die gesamte Zeichenfolge würde um 1 abweichen!) Ergibt keine messbaren Leistungssteigerungen (dh ich erhalte ± 2..3% Unterschiede, die mit Unterschieden verschiedener Läufe vergleichbar sind)char[]
Zuweisung zu eliminieren und den String unverändert zurückzugeben, wenn kein Strippen stattgefunden hat.Nun, ich habe die derzeit beste Methode (Freak-Lösung mit dem vorab zugewiesenen Array) nach meinen Maßstäben um etwa 30% übertroffen. Wie? Indem ich meine Seele verkaufe.
Wie ich sicher bin, weiß jeder, der die Diskussion bisher verfolgt hat, dass dies so ziemlich jedes grundlegende Programmierprinzip verletzt, aber na ja. Das Folgende funktioniert jedoch nur, wenn das verwendete Zeichenarray der Zeichenfolge nicht von anderen Zeichenfolgen gemeinsam genutzt wird. Wenn dies der Fall ist, hat jeder, der dies debuggen muss, das Recht, Sie zu töten (ohne Aufrufe von substring () und dies für Literalzeichenfolgen zu verwenden Dies sollte funktionieren, da ich nicht verstehe, warum die JVM eindeutige Zeichenfolgen interniert, die von einer externen Quelle gelesen wurden. Vergessen Sie jedoch nicht, sicherzustellen, dass der Benchmark-Code dies nicht tut - das ist äußerst wahrscheinlich und würde der Reflexionslösung offensichtlich helfen.
Sowieso hier gehen wir:
Für meinen Teststring wird das
3477148.18ops/s
vs.2616120.89ops/s
für die alte Variante. Ich bin mir ziemlich sicher, dass der einzige Weg, dies zu übertreffen, darin besteht, es in C zu schreiben (wahrscheinlich nicht) oder einen völlig anderen Ansatz, über den bisher noch niemand nachgedacht hat. Obwohl ich mir absolut nicht sicher bin, ob das Timing auf verschiedenen Plattformen stabil ist - liefert zumindest auf meiner Box (Java7, Win7 x64) zuverlässige Ergebnisse.quelle
new String()
falls Ihre Zeichenfolge nicht geändert wurde, aber ich denke, Sie haben das bereits.Sie können die Aufgabe abhängig von der Anzahl der Prozessoren in mehrere parallele Unteraufgaben aufteilen.
quelle
StringBuilder
überall problemlos verwenden kann.Ich war so frei und schrieb einen kleinen Benchmark für verschiedene Algorithmen. Es ist nicht perfekt, aber ich nehme das Minimum von 1000 Durchläufen eines bestimmten Algorithmus 10000 Mal über eine zufällige Zeichenfolge (standardmäßig mit etwa 32/200% nicht druckbaren Zeichen). Das sollte sich um Dinge wie GC, Initialisierung usw. kümmern - es gibt nicht so viel Overhead, dass ein Algorithmus nicht mindestens einen Lauf ohne große Hindernisse haben sollte.
Nicht besonders gut dokumentiert, aber na ja. Los geht's - ich habe sowohl die Algorithmen des Ratschenfreaks als auch die Basisversion aufgenommen. Im Moment initialisiere ich zufällig eine 200 Zeichen lange Zeichenfolge mit gleichmäßig verteilten Zeichen im Bereich [0, 200].
quelle
IANA Java-Performance-Junkie auf niedriger Ebene, aber haben Sie versucht, Ihre Hauptschleife abzuwickeln? ? Es scheint, dass es einigen CPUs ermöglichen könnte, Prüfungen parallel durchzuführen.
Auch dies hat einige lustige Ideen für Optimierungen.
quelle
Wenn Sie damit meinen
String#getBytes("utf-8")
usw.: Dies sollte nicht schneller sein - mit Ausnahme eines besseren Cachings - daCharset.forName("utf-8")
es intern verwendet wird, wenn der Zeichensatz nicht zwischengespeichert wird.Eine Sache könnte sein, dass Sie verschiedene Zeichensätze verwenden (oder dass ein Teil Ihres Codes transparent ist), aber der zwischengespeicherte Zeichensatz
StringCoding
ändert sich nicht.quelle