2012-03-26 26 views
16

¿Cuál es la mejor manera de tener la funcionalidad del método StreamReader.ReadLine(), pero con delimitadores personalizados (String)?C# StreamReader, "ReadLine" para los delimitadores personalizada

me gustaría hacer algo como:

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

he tratado de hacer mi propio uso de Peek() y StringBuilder, pero es demasiado ineficiente. Estoy buscando sugerencias o posiblemente una solución de código abierto.

Gracias.

Editar

que debería haber aclarado esto antes ... He visto this answer, sin embargo, preferiría no leer todo el archivo en la memoria.

+0

Por qué no usar ReadLine() y luego buscar delimitador en cadena? –

+0

Mediante el uso de 'Peek()' y 'StringBuilder' que está básicamente replicar lo' ReadLine() '' hacer dentro StreamReader' ... por lo que me parece extraño que sea tan lento; ¿puedes publicar lo que has intentado? – digEmAll

+0

ineficiente? ¿Qué tan ineficiente? Es el rendimiento que carece notablemente? –

Respuesta

2

que pensé que iba a publicar mi propia solución. Parece funcionar bastante bien y el código es relativamente simple. Siéntete libre de comentar

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

Sería un poco más claro (para mí) si pones un "descanso" justo después de "found = true" también. Requiere un poco menos de procesamiento mental. –

+3

Esta solución solo funciona en algunos casos. Por ejemplo, si el delimitador es "xy", este algoritmo perderá el delimitador en "axxyb" y lo leerá hasta el final de la secuencia. –

1

Este código debería funcionar para cualquier cadena separador.

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

responsabilidad:

hice un poco de pruebas en esto y en realidad es más lento que ReadLine método, pero sospecho que se debe a la puesta en cola/retirada de cola/SequenceEqual pide que en el método ReadLine puede evitarse (ya que el separador es siempre \r\n).

Una vez más, he hecho algunas pruebas y debería funcionar, pero no lo tome tan perfecta, y no dude en corregirlo. ;)

1

Aquí es un analizador sencilla utilicé cuando sea necesario (por lo general si el streaming no es una suma que acaba de leer y .split hace el trabajo), no muy optimizado, pero debería funcionar bien:
(que es más de una división como método - y más notas abajo)

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

... y simplemente pueden cambiar a char delimitadores solo si es necesario reemplazar el

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

... con

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

(y id++ en lugar de id+= y una firma this Stream stream, StringSplitOptions options, params char[] delimiters)

... también elimina vacío, etc.
creo que sirve

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