2010-08-17 9 views
19

Ich schreibe den Inhalt einer Textdatei in einen StringBuilder und ich möchte dann eine Reihe von Suchen/Ersetzen Aktionen auf den Text im StringBuilder mit regulären Ausdrücken durchführen.Regex Ersetzungen in einem StringBuilder

Ich habe ein Problem festgestellt, wie die StringBuilder Replace-Funktion keine Argumente für reguläre Ausdrücke akzeptieren kann.

Ich könnte Regex.Replace auf einer normalen Zeichenfolge verwenden, aber ich habe den Eindruck, dass dies ineffizient ist aufgrund der Tatsache, dass zwei Kopien der Zeichenfolge im Speicher erstellt werden müssen, da .net Zeichenfolgen unveränderlich sind.

Sobald ich den Text aktualisiert habe, plane ich, es wieder in die ursprüngliche Datei zu schreiben.

Was ist der beste und effizienteste Weg, um mein Problem zu lösen?

EDIT

Neben der Antwort (en) unten, habe ich folgende Fragen gefunden, die auch etwas Licht auf meinem Problem Schuppen -

Antwort

23

Die beste und effizienteste Lösung für Ihre Zeit ist der einfachste Ansatz zuerst zu versuchen: Vergessen Sie die StringBuilder und verwenden Sie einfach Regex.Replace. Dann finden Sie heraus, wie langsam es ist - es kann sehr gut gut genug sein. Vergessen Sie nicht, die Regex im kompilierten und nicht kompilierten Modus zu testen.

Wenn die nicht schnell genug ist, sollten Sie ein StringBuilder für alle Ersatz verwenden Sie einfach zum Ausdruck bringen können, und dann Regex.Replace für den Rest verwenden. Sie sollten auch in Erwägung ziehen, Ersetzungen zu kombinieren, indem Sie die Anzahl der verwendeten Regexes (und damit der Zwischenzeichenfolgen) reduzieren.

+1

Ich bin erstaunt, dass ich nicht darüber nachgedacht habe: tatsächlich laufen und sehen, anstatt darüber zu spekulieren, was die Geschwindigkeit wäre. Ich habe meine spekulative Antwort entsprechend gelöscht. – Timwi

+1

Wenn der Regex.Replace schnell genug war, sollte ich mich überhaupt um die Speicherverwaltung kümmern? Bin ich damit beschäftigt, Dinge zu analysieren/zu optimieren, indem ich mir Gedanken über die Speicherkapazität von mehreren Strings mache? – ipr101

+0

Dies ist keine Antwort so viel wie ein Vorschlag. Die Frage ist, wie Regex mit stringbuilder funktioniert, und die Antwort ist, dass sie nicht kompatibel sind, wenn Sie nicht Ihre eigene Implementierung schreiben. Warum das der Fall ist, weiß ich nicht. – Slight

1

Ich bin mir nicht sicher, ob dies Ihrem Szenario hilft oder nicht, aber ich stieß auf einige Speicherverbrauchsgrenzen mit Regex und ich brauchte eine einfache Platzhalterersetzungs-Erweiterungsmethode auf einem StringBuilder, um daran vorbei zu schieben. Wenn Sie komplexe Regex-Abgleiche und/oder Rückreferenzen benötigen, ist dies nicht möglich, aber wenn Sie einfach * oder? Wildcard-Ersatz (mit normalen Text „ersetzen“) würde die Arbeit für Sie erledigen, dann die Problemumgehung am Ende meiner Frage hier sollten Ihnen zumindest einen Schub geben:

Has anyone implemented a Regex and/or Xml parser around StringBuilders or Streams?

0

Hier ist eine Erweiterung Methode, die Sie könnte verwenden, um zu erreichen, was Sie wollen. Es nimmt eine Dictionary, wo der Schlüssel das Muster ist, das Sie suchen, und der Wert ist, was Sie es mit ersetzen möchten. Sie erstellen weiterhin Kopien der eingehenden Zeichenfolge, müssen diese jedoch nur einmal bearbeiten, anstatt Kopien für mehrere Aufrufe an Regex.Replace zu erstellen.

public static StringBuilder BulkReplace(this StringBuilder source, IDictionary<string, string> replacementMap) 
{ 
    if (source.Length == 0 || replacementMap.Count == 0) 
    { 
     return source; 
    } 
    string replaced = Regex.Replace(source.ToString(), String.Join("|", replacementMap.Keys.Select(Regex.Escape).ToArray()), m => replacementMap[m.Value], RegexOptions.IgnoreCase); 
    return source.Clear().Append(replaced); 
} 
+1

Der Sinn der Verwendung von Regex mit StringBuilder besteht nicht einfach darin, eine Methode zu entwickeln, die den Job erledigt, sondern um Speicherverschwendung zu minimieren, insbesondere indem vermieden wird, dass eine große Anzahl von intermediären Strings im Speicher abgelegt wird. –

+0

Es ist nicht perfekt, weil Sie den StringBuilder in eine Zeichenfolge konvertieren müssen, aber diese Methode ist etwa 4 mal schneller als einfach Regex.Replace auf eine Zeichenfolge immer wieder aufrufen. –

+0

Wenn die replacementMap Muster enthält, erhalten Sie: "Der angegebene Schlüssel war nicht im Wörterbuch vorhanden". Dies wird erwartet, da der m.Wert von der replacementMap [m.Value] nach einem Schlüssel sucht, bei dem es sich um die Actula-Zeichenfolge handelt, die dem Muster und nicht dem Muster selbst entspricht. Fehle ich etwas? Mit Mustern meine ich Regex-Muster wie: "" <[^>] +> "und nicht exakte Strings wie"
mmmmmm

2

Sie haben 3 Optionen:

  1. dies mit Streichern in einer ineffizienten Weise tun, wie andere hier empfohlen.

  2. Verwenden Sie den .Matches() Anruf auf Ihrem Regex Objekt, und die Art und Weise .Replace() Werke zu emulieren (# 3 sehen).

  3. Passen Sie die Mono Implementierung von Regex ein Regex zu bauen, die StringBuilder (und bitte teilen Sie es hier!) Akzeptiert Fast alle der Arbeit bereits für Sie in Mono getan wird, aber es wird einige Zeit dauern, die Teile abtasten, dass Lass es in eine eigene Bibliothek arbeiten. Monos Regex nutzt Novells JVM-Implementierung von 2002 Regex, seltsam genug.

In Mono:

System.Text.RegularExpressions.Regex verwendet einen RxCompiler eine IMachineFactory in Form eines RxInterpreterFactory, zu instanziiert, die wenig überraschend IMachine s als RxInterpreter s macht. Diese zu emittieren ist das meiste, was Sie tun müssen. Wenn Sie jedoch nur herausfinden möchten, wie alles nach Effizienz strukturiert ist, ist es in der Basisklasse BaseMachine bemerkenswert.

Insbesondere in BaseMachine ist die StringBuilder-basierte Sachen. In der Methode LTRReplace instanziiert es zuerst einen StringBuilder mit der Anfangszeichenkette, und alles von da an ist rein StringBuilder-basiert. Es ist eigentlich sehr ärgerlich, dass Regex keine StringBuilder-Methoden hat, wenn wir annehmen, dass die interne Microsoft .Net-Implementierung ähnlich ist.

Circling zurück zur Anregung 2 können Sie LTRReplace ‚s Verhalten von .Matches() Aufruf, Tracking, wo Sie sind in der ursprünglichen Zeichenfolge, und Looping imitieren:

var matches = regex.Matches(original); 
var sb = new StringBuilder(original.Length); 
int pos = 0; // position in original string 
foreach(var match in matches) 
{ 
    sb.Append(original.Substring(pos, match.Index)); // Append the portion of the original we skipped 
    pos = match.Index; 

    // Make any operations you like on the match result, like your own custom Replace, or even run another Regex 

    pos += match.Value.Length; 
} 
sb.Append(original.Substring(pos, original.Length - 1)); 

Aber dies spart Ihnen einige Saiten - die Mod-Mono-Ansatz ist der einzige, der es richtig macht.