2012-03-26 18 views
16

Was ist der beste Weg, um die Funktionalität der StreamReader.ReadLine() Methode, aber mit benutzerdefinierten (String) Trennzeichen zu haben?C# StreamReader, "ReadLine" für benutzerdefinierte Trennzeichen

Ich möchte etwas tun:

String text; 
while((text = myStreamReader.ReadUntil("my_delim")) != null) 
{ 
    Console.WriteLine(text); 
} 

Ich versuchte, meine eigenen zu machen Peek() und StringBuilder verwenden, aber es ist zu ineffizient. Ich suche nach Vorschlägen oder einer Open-Source-Lösung.

Danke.

bearbeiten

ich dies früher geklärt haben sollte ... Ich habe this answer gesehen, würde ich aber lieber nicht die gesamte Datei in den Speicher lesen.

+0

Warum mit Readline nicht() und dann für Trennzeichen in Zeichenfolge suchen? –

+0

Mit 'Peek()' und 'StringBuilder' replizierst du im Grunde, was' ReadLine() 'in' StreamReader' tut ... so seltsam erscheint es mir, dass es so langsam ist; kannst du posten, was du versucht hast? – digEmAll

+0

Ineffizient? Wie ineffizient? Fehlt die Leistung spürbar? –

Antwort

2

hilft ich dachte, ich würde meine eigene Lösung erstellen. Es scheint ziemlich gut zu funktionieren und der Code ist relativ einfach. Fühlen Sie sich frei zu kommentieren.

public static String ReadUntil(this StreamReader sr, String delim) 
{ 
    StringBuilder sb = new StringBuilder(); 
    bool found = false; 

    while (!found && !sr.EndOfStream) 
    { 
     for (int i = 0; i < delim.Length; i++) 
     { 
      Char c = (char)sr.Read(); 
      sb.Append(c); 

      if (c != delim[i]) 
       break; 

      if (i == delim.Length - 1) 
      { 
       sb.Remove(sb.Length - delim.Length, delim.Length); 
       found = true; 
      } 
     } 
    } 

    return sb.ToString(); 
} 
+1

Es wäre etwas klarer (für mich), wenn Sie eine Pause gleich nach "found = true" setzen würden. Benötigt ein bisschen weniger mentale Verarbeitung. –

+3

Diese Lösung funktioniert nur in einigen Fällen. Zum Beispiel, wenn der Begrenzer "xy" ist, dann wird dieser Algorithmus das Trennzeichen in "axxyb" verpassen und es wird bis zum Ende des Streams gelesen. –

1

Dieser Code sollte für alle Zeichenfolgentrennzeichen funktionieren.

public static IEnumerable<string> ReadChunks(this TextReader reader, string chunkSep) 
{ 
    var sb = new StringBuilder(); 

    var sepbuffer = new Queue<char>(chunkSep.Length); 
    var sepArray = chunkSep.ToCharArray(); 

    while (reader.Peek() >= 0) 
    { 
     var nextChar = (char)reader.Read(); 
     if (nextChar == chunkSep[sepbuffer.Count]) 
     { 
      sepbuffer.Enqueue(nextChar); 
      if (sepbuffer.Count == chunkSep.Length) 
      { 
       yield return sb.ToString(); 
       sb.Length = 0; 
       sepbuffer.Clear(); 
      } 
     } 
     else 
     { 
      sepbuffer.Enqueue(nextChar); 
      while (sepbuffer.Count > 0) 
      { 
       sb.Append(sepbuffer.Dequeue()); 
       if (sepbuffer.SequenceEqual(chunkSep.Take(sepbuffer.Count))) 
        break; 
      } 
     } 
    } 
    yield return sb.ToString() + new string(sepbuffer.ToArray()); 
} 

Haftungsausschluss:

machte ich einen kleinen Test auf diesem und ist tatsächlich langsamer als ReadLine Methode, aber ich vermute, dass es an die Enqueue gebührt/dequeue/SequenceEqual Anrufe, die in den ReadLine Verfahren können vermieden werden (weil das Trennzeichen immer \r\n ist).

Noch einmal, ich habe ein paar Tests gemacht und es sollte funktionieren, aber nicht so perfekt, und fühlen Sie sich frei, es zu korrigieren. ;)

1

Hier ist ein einfacher Parser ich dort eingesetzt, wo erforderlich (in der Regel, wenn Streaming nicht an erster Stelle steht nur lesen und .split macht den Job), nicht zu optimieren, aber soll funktionieren:
(es eher ein Split ist wie Verfahren - und mehr Hinweise unten)

public static IEnumerable<string> Split(this Stream stream, string delimiter, StringSplitOptions options) 
    { 
     var buffer = new char[_bufffer_len]; 
     StringBuilder output = new StringBuilder(); 
     int read; 
     using (var reader = new StreamReader(stream)) 
     { 
      do 
      { 
       read = reader.ReadBlock(buffer, 0, buffer.Length); 
       output.Append(buffer, 0, read); 

       var text = output.ToString(); 
       int id = 0, total = 0; 
       while ((id = text.IndexOf(delimiter, id)) >= 0) 
       { 
        var line = text.Substring(total, id - total); 
        id += delimiter.Length; 
        if (options != StringSplitOptions.RemoveEmptyEntries || line != string.Empty) 
         yield return line; 
        total = id; 
       } 
       output.Remove(0, total); 
      } 
      while (read == buffer.Length); 
     } 

     if (options != StringSplitOptions.RemoveEmptyEntries || output.Length > 0) 
      yield return output.ToString(); 
    } 

... und Sie können einfach wechseln Trennzeichen verkohlen, wenn die

while ((id = text.IndexOf(delimiter, id)) >= 0) 

nur ersetzen benötigt ... mit

while ((id = text.IndexOfAny(delimiters, id)) >= 0) 

(und id++ statt id+= und eine Signatur this Stream stream, StringSplitOptions options, params char[] delimiters)

... entfernt auch leer usw.
hoffen, dass es

0
public static String ReadUntil(this StreamReader streamReader, String delimiter) 
    { 
     StringBuilder stringBuilder = new StringBuilder(); 

     while (!streamReader.EndOfStream) 
     { 
      stringBuilder.Append(value: (Char) streamReader.Read()); 

      if (stringBuilder.ToString().EndsWith(value: delimiter)) 
      { 
       stringBuilder.Remove(stringBuilder.Length - delimiter.Length, delimiter.Length); 
       break; 
      } 
     } 

     return stringBuilder.ToString(); 
    }