2012-03-26 9 views
16

Qual è il modo migliore per avere la funzionalità del metodo StreamReader.ReadLine(), ma con delimitatori personalizzati (String)?C# StreamReader, "ReadLine" Per delimitatori personalizzati

mi piacerebbe fare qualcosa di simile:

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

ho cercato di fare il mio utilizzando Peek() e StringBuilder, ma è troppo inefficiente. Sto cercando suggerimenti o forse una soluzione open-source.

Grazie.

Modifica

, avrei chiarito questo prima ... ho visto this answer, però, preferisco non leggere l'intero file in memoria.

+0

Perché non usando ReadLine() e quindi cercare delimitatore nella stringa? –

+0

Usando 'Peek()' e 'StringBuilder' stai praticamente replicando cosa' ReadLine() 'fa all'interno di' StreamReader' ... quindi mi sembra strano che sia così lento; puoi pubblicare ciò che hai provato? – digEmAll

+0

Inefficiente? Quanto inefficiente? Le prestazioni mancano sensibilmente? –

risposta

2

ho pensato avrei post la mia soluzione. Sembra funzionare abbastanza bene e il codice è relativamente semplice. Sentiti libero di commentare.

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

Sarebbe leggermente più chiaro (per me) se metti una "pausa" subito dopo "found = true". Richiede un po 'meno elaborazione mentale. –

+3

Questa soluzione funziona solo in alcuni casi. Ad esempio, se il delimitatore è "xy", questo algoritmo mancherà il delimitatore in "axxyb" e leggerà fino alla fine del flusso. –

1

Questo codice dovrebbe funzionare per qualsiasi separatore di stringhe.

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()); 
} 

responsabilità:

Ho fatto un piccolo test su questo e in realtà è più lento di ReadLine metodo, ma ho il sospetto che è dovuto al accodamento/dequeue/SequenceEqual chiamate che nel metodo ReadLine può essere evitato (perché il separatore è sempre \r\n).

Ancora una volta, ho fatto alcuni test e dovrebbe funzionare, ma non prenderlo come perfetto, e sentitevi liberi di correggerlo. ;)

1

Ecco un semplice parser ho usato dove serve (di solito se lo streaming non è un fondamentale appena letto e .Split fa il lavoro), non troppo ottimizzato, ma dovrebbe funzionare bene:
(è più di una scissione come metodo - più note qui sotto)

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(); 
    } 

... e si può semplicemente passare a char delimitatori, se necessario, basta sostituire il

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

... con

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

(e id++ invece di id+= e una firma this Stream stream, StringSplitOptions options, params char[] delimiters)

... rimuove anche vuoto ecc
Speranza che aiuta

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(); 
    }