Der schnellste und einfachste Weg, eine Reihe von Zeichen aus einer Zeichenfolge zu entfernen, ist die Verwendung von StringBuilder + List
public static string RemoveChars(string input, List<char> charsToRemove)
{
if (string.IsNullOrEmpty(input))
return input;
var sb = new StringBuilder();
foreach (var c in input)
{
if (!charsToRemove.Contains(c))
sb.Append(c);
}
return sb.ToString();
}
Code language: C# (cs)
Ich habe dies mit drei anderen Ansätzen verglichen. Ich habe 100.000 Iterationen mit einer Zeichenfolge mit 2500 Zeichen und einer Liste von 15 zu entfernenden Zeichen durchgeführt. Dieser StringBuilder-Ansatz ist fast doppelt so schnell wie der zweitschnellste Ansatz.
Hier ist die Zusammenfassung der Leistungsstatistiken für alle Ansätze:
Ansatz | Gesamt (ms) | Durchschnitt (ms) | Minute (ms) | Max (ms) |
StringBuilder | 4251.91 | 0,042 | 0,036 | 0,42 |
LINQ + neue Zeichenfolge() + ToArray() | 7176.47 | 0,071 | 0,047 | 0,74 |
LINQ + string.Concat() | 8485.75 | 0,085 | 0,059 | 1,64 |
Regex | 31368.22 | 0,31 | 0,25 | 2,45 |
Ein überraschendes Ergebnis ist, dass List
Im Rest dieses Artikels zeige ich den Code für die anderen Ansätze, die ich verglichen habe, und zeige, wie ich die Leistung gemessen und verglichen habe.
Andere Ansätze
Die folgenden Ansätze sind langsamer als der StringBuilder-Ansatz. Die LINQ-Ansätze können als subjektiv einfacher angesehen werden als der StringBuilder-Ansatz (wenn Sie LINQ gegenüber Foreach-Schleifen bevorzugen).
LINQ + neue Zeichenfolge() + ToArray()
Dies verwendet LINQ, um Zeichen herauszufiltern, und verwendet dann new string() + ToArray(), um das Ergebnis in eine Zeichenfolge umzuwandeln:
public static string RemoveChars(string input, List<char> charsToRemove)
{
if (string.IsNullOrEmpty(input))
return input;
return new string(input.Where(c => !charsToRemove.Contains(c)).ToArray());
}
Code language: C# (cs)
Die Leistungsstatistik:
Total Time: 7176.47ms Avg=0.071ms Min=0.047ms Max=0.74ms
Code language: plaintext (plaintext)
LINQ + string.Concat()
Dies verwendet LINQ, um die Zeichen zu filtern, und verwendet dann Concat(), um das Ergebnis in einen String umzuwandeln:
public static string RemoveChars(string input, List<char> charsToRemove)
{
if (string.IsNullOrEmpty(input))
return input;
return string.Concat(input.Where(c => !charsToRemove.Contains(c)));
}
Code language: C# (cs)
Die Leistungsstatistik:
Total Time: 8485.75ms Avg=0.085ms Min=0.059ms Max=1.64ms
Code language: plaintext (plaintext)
Regex
Die Verwendung von Regex für dieses Problem ist keine gute Idee. Es ist der langsamste und am wenigsten einfache Ansatz:
static Regex charsToRemoveRegex = new Regex("[<>?;&*=~^+|:,/m]", RegexOptions.Compiled);
public static string RemoveChars(string input)
{
if (string.IsNullOrEmpty(input))
return input;
return charsToRemoveRegex.Replace(input, "");
}
Code language: C# (cs)
Die Leistungsstatistik:
Total Time: 31368.22ms Avg=0.31ms Min=0.25ms Max=2.45ms
Code language: plaintext (plaintext)
Autsch, das ist langsam.
Ansatz zum Leistungsvergleich
Für jeden Ansatz habe ich 100.000 Iterationen durchgeführt und eine Zeichenfolge der Länge 2500 mit einer Liste von 15 zu entfernenden Zeichen verwendet.
Wenn Sie die Leistung vergleichen, ist es eine gute Idee, die Gesamt-, Durchschnitts-, Mindest- und Höchstzeiten zu überprüfen. Nicht nur Verlassen Sie sich auf die Summe und den Durchschnitt. Das Minimum und das Maximum geben die Breite der Verteilung der Ausführungszeiten an. Je enger die Verteilung, desto besser. Wenn Sie sich die Leistungsübersichtstabelle ansehen, stellen Sie fest, dass der StringBuilder-Ansatz die beste durchschnittliche Zeit und auch die engste Verteilung der Ausführungszeiten aufweist.
Die erste Ausführung eines beliebigen Codes ist immer langsamer als nachfolgende Ausführungen. Wenn Sie also die Leistung vergleichen, ist es immer eine gute Idee, den Code „aufzuwärmen“ oder das erste Ausführungsergebnis zu verwerfen, damit es die Ergebnisse nicht wesentlich verzerrt. Ich protokolliere die erste Ausführung (und zeige, dass es immer das Maximum ist) und verwerfe sie dann.
Hier ist der Code, den ich verwendet habe, um die Leistung jedes Ansatzes zu testen:
static void Main(string[] args)
{
List<char> charsToRemove = new List<char>
{
'<','>','?',';','&','*',
'=','~','^', '+','|',':',','
,'/','m'
};
var testSb = new StringBuilder();
for(int i = 0; i < 100; i++)
{
testSb.Append("<>?hello;&*=~world^+|:,/m");
}
var testString = testSb.ToString();
Console.WriteLine(testString.Length);
List<double> elapsedMS = new List<double>();
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 100_000; i++)
{
var cleanedString = RemoveChars(testString.ToString(), charsToRemove);
elapsedMS.Add(sw.Elapsed.TotalMilliseconds);
sw.Restart();
}
sw.Stop();
//First() is always much larger and skews the Sum() and Average(). Print it here, but then remove it for the other aggregates
Console.WriteLine($"First={elapsedMS.First()}ms Max={elapsedMS.First()}ms");
elapsedMS.RemoveAt(0);
Console.WriteLine($"Total Time: {elapsedMS.Sum()}ms Avg={elapsedMS.Average()}ms Min={elapsedMS.Min()}ms Max={elapsedMS.Max()}ms");
}
Code language: C# (cs)