Ich habe eine Zeichenfolge, in der ich Markierungen durch Werte aus einem Wörterbuch ersetzen muss. Es muss so effizient wie möglich sein. Wenn Sie eine Schleife mit einem string.replace ausführen, wird nur Speicher verbraucht (Strings sind unveränderlich, denken Sie daran). Wäre StringBuilder.Replace () besser, da dies für die Bearbeitung von String-Manipulationen entwickelt wurde?
Ich hatte gehofft, die Kosten von RegEx zu vermeiden, aber wenn das effizienter wird, dann soll es so sein.
Hinweis: Die Komplexität des Codes ist mir egal, nur wie schnell er ausgeführt wird und wie viel Speicher er verbraucht.
Durchschnittliche Statistik: 255-1024 Zeichen lang, 15-30 Schlüssel im Wörterbuch.
Antworten:
Verwenden von RedGate Profiler mit dem folgenden Code
class Program { static string data = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; static Dictionary<string, string> values; static void Main(string[] args) { Console.WriteLine("Data length: " + data.Length); values = new Dictionary<string, string>() { { "ab", "aa" }, { "jk", "jj" }, { "lm", "ll" }, { "yz", "zz" }, { "ef", "ff" }, { "st", "uu" }, { "op", "pp" }, { "x", "y" } }; StringReplace(data); StringBuilderReplace1(data); StringBuilderReplace2(new StringBuilder(data, data.Length * 2)); Console.ReadKey(); } private static void StringReplace(string data) { foreach(string k in values.Keys) { data = data.Replace(k, values[k]); } } private static void StringBuilderReplace1(string data) { StringBuilder sb = new StringBuilder(data, data.Length * 2); foreach (string k in values.Keys) { sb.Replace(k, values[k]); } } private static void StringBuilderReplace2(StringBuilder data) { foreach (string k in values.Keys) { data.Replace(k, values[k]); } } }
Stringlänge = 1456
Stringbuilder Nr. 1 erstellt den Stringbuilder in der Methode, während Nr. 2 dies nicht tut, sodass der Leistungsunterschied höchstwahrscheinlich gleich bleibt, da Sie nur diese Arbeit aus der Methode entfernen. Wenn Sie mit einem Stringbuilder anstelle eines Strings beginnen, ist möglicherweise # 2 der richtige Weg.
In Bezug auf den Arbeitsspeicher müssen Sie sich mit dem RedGateMemory-Profiler keine Sorgen machen, bis Sie zu VIELEN Ersetzungsvorgängen gelangen, bei denen der Stringbuilder insgesamt gewinnen wird.
quelle
Dies kann hilfreich sein:
http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance.aspx
Die kurze Antwort scheint zu sein, dass String.Replace schneller ist, obwohl dies einen größeren Einfluss auf Ihren Speicherbedarf / Speicherbereinigungsaufwand haben kann.
quelle
Ja,
StringBuilder
Sie erhalten sowohl Geschwindigkeits- als auch Speichergewinn (im Grunde genommen, weil nicht jedes Mal, wenn Sie eine Manipulation damit durchführen, eine Instanz einer Zeichenfolge erstellt wird - arbeitetStringBuilder
immer mit demselben Objekt). Hier ist ein MSDN-Link mit einigen Details.quelle
Ja, viel besser. Und wenn Sie eine Obergrenze für die neue Zeichenfolge schätzen können (es sieht so aus, als könnten Sie das), ist sie wahrscheinlich schnell genug.
Wenn Sie es erstellen wie:
var sb = new StringBuilder(inputString, pessimisticEstimate);
dann muss der StringBuilder seinen Puffer nicht neu zuordnen.
quelle
Das Konvertieren von Daten von einem String in einen StringBuilder und zurück dauert einige Zeit. Wenn nur eine einzige Ersetzungsoperation ausgeführt wird, wird diese Zeit möglicherweise nicht durch die Effizienzverbesserungen von StringBuilder wieder wettgemacht. Wenn man dagegen einen String in einen StringBuilder konvertiert, dann viele Ersetzungsoperationen daran ausführt und ihn am Ende wieder konvertiert, ist der StringBuilder-Ansatz wahrscheinlich schneller.
quelle
Anstatt 15 bis 30 Ersetzungsoperationen für die gesamte Zeichenfolge auszuführen, ist es möglicherweise effizienter, so etwas wie eine Trie- Datenstruktur zu verwenden, um Ihr Wörterbuch zu speichern. Anschließend können Sie Ihre Eingabezeichenfolge einmal durchlaufen, um alle Suchvorgänge / Ersetzungen durchzuführen.
quelle
Es hängt stark davon ab, wie viele der Marker durchschnittlich in einer bestimmten Zeichenfolge vorhanden sind.
Die Leistung bei der Suche nach einem Schlüssel ist zwischen StringBuilder und String wahrscheinlich ähnlich, aber StringBuilder gewinnt, wenn Sie viele Markierungen in einer einzelnen Zeichenfolge ersetzen müssen.
Wenn Sie durchschnittlich nur ein oder zwei Marker pro Zeichenfolge erwarten und Ihr Wörterbuch klein ist, würde ich mich einfach für String.Replace entscheiden.
Wenn es viele Markierungen gibt, möchten Sie möglicherweise eine benutzerdefinierte Syntax definieren, um Markierungen zu identifizieren - z. B. in geschweiften Klammern mit einer geeigneten Escape-Regel für eine wörtliche Klammer. Sie können dann einen Parsing-Algorithmus implementieren, der die Zeichen der Zeichenfolge einmal durchläuft und jeden gefundenen Marker erkennt und ersetzt. Oder verwenden Sie einen regulären Ausdruck.
quelle
MatchEvaluator
, um die Wörterbuchsuche tatsächlich durchzuführen.Meine zwei Cent hier, ich habe nur ein paar Codezeilen geschrieben, um zu testen, wie jede Methode funktioniert, und wie erwartet ist das Ergebnis "es kommt darauf an".
Bei längeren Saiten
Regex
scheint die Leistung besser zu sein, bei kürzeren Saiten istString.Replace
dies der Fall . Ich kann sehen, dass die Verwendung vonStringBuilder.Replace
nicht sehr nützlich ist, und wenn sie falsch verwendet wird, kann sie in der GC-Perspektive tödlich sein (ich habe versucht, eine Instanz von zu teilenStringBuilder
).Überprüfen Sie mein StringReplaceTests GitHub-Repo .
quelle
Das Problem mit der Antwort von @DustinDavis ist, dass es rekursiv mit derselben Zeichenfolge arbeitet. Sofern Sie nicht vorhaben, eine Hin- und Her-Manipulation durchzuführen, sollten Sie für diese Art von Test wirklich separate Objekte für jeden Manipulationsfall haben.
Ich habe mich entschlossen, meinen eigenen Test zu erstellen, weil ich im gesamten Web einige widersprüchliche Antworten gefunden habe, und ich wollte ganz sicher sein. Das Programm, an dem ich arbeite, behandelt viel Text (in einigen Fällen Dateien mit Zehntausenden von Zeilen).
Hier ist eine schnelle Methode, die Sie kopieren und einfügen können, um selbst zu sehen, welche schneller ist. Möglicherweise müssen Sie zum Testen eine eigene Textdatei erstellen. Sie können jedoch problemlos Text von überall kopieren und einfügen und eine ausreichend große Datei für sich selbst erstellen:
using System; using System.Diagnostics; using System.IO; using System.Text; using System.Windows; void StringReplace_vs_StringBuilderReplace( string file, string word1, string word2 ) { using( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) ) using( StreamReader streamReader = new StreamReader( fileStream, Encoding.UTF8 ) ) { string text = streamReader.ReadToEnd(), @string = text; StringBuilder @StringBuilder = new StringBuilder( text ); int iterations = 10000; Stopwatch watch1 = new Stopwatch.StartNew(); for( int i = 0; i < iterations; i++ ) if( i % 2 == 0 ) @string = @string.Replace( word1, word2 ); else @string = @string.Replace( word2, word1 ); watch1.Stop(); double stringMilliseconds = watch1.ElapsedMilliseconds; Stopwatch watch2 = new Stopwatch.StartNew(); for( int i = 0; i < iterations; i++ ) if( i % 2 == 0 ) @StringBuilder = @StringBuilder .Replace( word1, word2 ); else @StringBuilder = @StringBuilder .Replace( word2, word1 ); watch2.Stop(); double StringBuilderMilliseconds = watch1.ElapsedMilliseconds; MessageBox.Show( string.Format( "string.Replace: {0}\nStringBuilder.Replace: {1}", stringMilliseconds, StringBuilderMilliseconds ) ); } }
Ich habe diese Zeichenfolge erhalten. Ersetzen () war jedes Mal, wenn Wörter mit 8 bis 10 Buchstaben ausgetauscht wurden, um etwa 20% schneller. Probieren Sie es selbst aus, wenn Sie Ihre eigenen empirischen Beweise wollen.
quelle