2011-01-06 10 views
13

necesita un fragmento de código que leería las últimas "n líneas" de un archivo de registro. Se me ocurrió el siguiente código de la red. Soy un poco nuevo en C sharp. Dado que el archivo de registro puede ser bastante grande, quiero evitar la sobrecarga de la lectura del archivo completo. ¿Puede alguien sugerir alguna mejora en el rendimiento? I realmente no quiero leer cada carácter y cambiar de posición.Cómo leer las últimas "n" líneas del archivo de registro

var reader = new StreamReader(filePath, Encoding.ASCII); 
      reader.BaseStream.Seek(0, SeekOrigin.End); 
      var count = 0; 
      while (count <= tailCount) 
      { 
       if (reader.BaseStream.Position <= 0) break; 
       reader.BaseStream.Position--; 
       int c = reader.Read(); 
       if (reader.BaseStream.Position <= 0) break; 
       reader.BaseStream.Position--; 
       if (c == '\n') 
       { 
        ++count; 
       } 
      } 

      var str = reader.ReadToEnd(); 
+0

No puede utilizar un StreamReader así. – SLaks

+0

eche un vistazo a http://stackoverflow.com/questions/1271225/c-reading-a-file-line-by-line. Luego puede usar la extensión LINQ '.Last()' en IEnumerable para obtener las últimas N líneas –

+0

@Russ: No, no puede. LINQ no puede proporcionarle eficientemente las últimas _n_ líneas. – SLaks

Respuesta

9

Su código tendrá un rendimiento muy pobre, ya que no está permitiendo el almacenamiento en caché.
Además, no funcionará en total para Unicode.

me escribió la siguiente implementación:

///<summary>Returns the end of a text reader.</summary> 
///<param name="reader">The reader to read from.</param> 
///<param name="lineCount">The number of lines to return.</param> 
///<returns>The last lneCount lines from the reader.</returns> 
public static string[] Tail(this TextReader reader, int lineCount) { 
    var buffer = new List<string>(lineCount); 
    string line; 
    for (int i = 0; i < lineCount; i++) { 
     line = reader.ReadLine(); 
     if (line == null) return buffer.ToArray(); 
     buffer.Add(line); 
    } 

    int lastLine = lineCount - 1;   //The index of the last line read from the buffer. Everything > this index was read earlier than everything <= this indes 

    while (null != (line = reader.ReadLine())) { 
     lastLine++; 
     if (lastLine == lineCount) lastLine = 0; 
     buffer[lastLine] = line; 
    } 

    if (lastLine == lineCount - 1) return buffer.ToArray(); 
    var retVal = new string[lineCount]; 
    buffer.CopyTo(lastLine + 1, retVal, 0, lineCount - lastLine - 1); 
    buffer.CopyTo(0, retVal, lineCount - lastLine - 1, lastLine + 1); 
    return retVal; 
} 
+2

me gustó mucho la idea de cambiar el búfer. Pero esto no leerá de manera efectiva todo el archivo de registro. ¿Hay una manera efectiva de "buscar" hasta el inicio de la línea ny hacer una línea de lectura() a partir de ahí. ¡Esto podría ser una tonta duda mía! – frictionlesspulley

+2

@frictionlesspulley: prueba http://stackoverflow.com/questions/398378/get-last-10-lines-of-verylarge-text-file-10gb-c/398512#398512 – SLaks

0

Algo que ahora se puede hacer muy fácilmente en C# 4.0 (y con sólo un poco de esfuerzo en las versiones anteriores) es el uso de memoria mapeada archivos para este tipo de operación. Es ideal para archivos grandes porque puede asignar solo una parte del archivo y luego acceder a ella como memoria virtual.

Hay un good example here.

+0

Esta es una buena idea, sin embargo Por lo que yo entiendo, no permite leer archivos por líneas (texto) como pregunta. – AaA

4

Un amigo mío usa this method (BackwardReader se puede encontrar here):

public static IList<string> GetLogTail(string logname, string numrows) 
{ 
    int lineCnt = 1; 
    List<string> lines = new List<string>(); 
    int maxLines; 

    if (!int.TryParse(numrows, out maxLines)) 
    { 
     maxLines = 100; 
    } 

    string logFile = HttpContext.Current.Server.MapPath("~/" + logname); 

    BackwardReader br = new BackwardReader(logFile); 
    while (!br.SOF) 
    { 
     string line = br.Readline(); 
     lines.Add(line + System.Environment.NewLine); 
     if (lineCnt == maxLines) break; 
     lineCnt++; 
    } 
    lines.Reverse(); 
    return lines; 
} 
+3

** ¿POR QUÉ ** es 'numrows' una cadena? – SLaks

+0

La misma pregunta que SLaks, pero +1 para 'BackwardReader'. No sabía sobre eso. – BrunoLM

+0

Seré honesto, SLaks, no puedo encontrar nada en la publicación del blog de mi amigo que explique por qué. Puedo ver que es esencialmente un método WCF llamado desde JavaScript, pero no estoy seguro si eso lo explica adecuadamente. –

0

¿Tiene su registro de líneas de longitud similar? Si es así, entonces se puede calcular la longitud media de la línea, a continuación, hacer lo siguiente:

  1. buscan end_of_file - lines_needed * avg_line_length (previous_point)
  2. leer todo hasta el final
  3. si agarraste lo suficientemente líneas, está bien. Si no, tratar de previous_point - lines_needed * avg_line_length
  4. leer todo hasta previous_point
  5. Goto 3

archivo asignado en memoria es también un buen método - un mapa de la cola del archivo, el cálculo de líneas, mapa el bloque anterior, calcular líneas etc., hasta que se obtiene el número de líneas necesarias

2

Aquí está mi respuesta: -

private string StatisticsFile = @"c:\yourfilename.txt"; 

    // Read last lines of a file.... 
    public IList<string> ReadLastLines(int nFromLine, int nNoLines, out bool bMore) 
    { 
     // Initialise more 
     bMore = false; 
     try 
     { 
      char[] buffer = null; 
      //lock (strMessages) Lock something if you need to.... 
      { 
       if (File.Exists(StatisticsFile)) 
       { 
        // Open file 
        using (StreamReader sr = new StreamReader(StatisticsFile)) 
        { 
         long FileLength = sr.BaseStream.Length; 

         int c, linescount = 0; 
         long pos = FileLength - 1; 
         long PreviousReturn = FileLength; 
         // Process file 
         while (pos >= 0 && linescount < nFromLine + nNoLines) // Until found correct place 
         { 
          // Read a character from the end 
          c = BufferedGetCharBackwards(sr, pos); 
          if (c == Convert.ToInt32('\n')) 
          { 
           // Found return character 
           if (++linescount == nFromLine) 
            // Found last place 
            PreviousReturn = pos + 1; // Read to here 
          } 
          // Previous char 
          pos--; 
         } 
         pos++; 
         // Create buffer 
         buffer = new char[PreviousReturn - pos]; 
         sr.DiscardBufferedData(); 
         // Read all our chars 
         sr.BaseStream.Seek(pos, SeekOrigin.Begin); 
         sr.Read(buffer, (int)0, (int)(PreviousReturn - pos)); 
         sr.Close(); 
         // Store if more lines available 
         if (pos > 0) 
          // Is there more? 
          bMore = true; 
        } 
        if (buffer != null) 
        { 
         // Get data 
         string strResult = new string(buffer); 
         strResult = strResult.Replace("\r", ""); 

         // Store in List 
         List<string> strSort = new List<string>(strResult.Split('\n')); 
         // Reverse order 
         strSort.Reverse(); 

         return strSort; 
        } 
       } 
      } 
     } 
     catch (Exception ex) 
     { 
      System.Diagnostics.Debug.WriteLine("ReadLastLines Exception:" + ex.ToString()); 
     } 
     // Lets return a list with no entries 
     return new List<string>(); 
    } 

    const int CACHE_BUFFER_SIZE = 1024; 
    private long ncachestartbuffer = -1; 
    private char[] cachebuffer = null; 
    // Cache the file.... 
    private int BufferedGetCharBackwards(StreamReader sr, long iPosFromBegin) 
    { 
     // Check for error 
     if (iPosFromBegin < 0 || iPosFromBegin >= sr.BaseStream.Length) 
      return -1; 
     // See if we have the character already 
     if (ncachestartbuffer >= 0 && ncachestartbuffer <= iPosFromBegin && ncachestartbuffer + cachebuffer.Length > iPosFromBegin) 
     { 
      return cachebuffer[iPosFromBegin - ncachestartbuffer]; 
     } 
     // Load into cache 
     ncachestartbuffer = (int)Math.Max(0, iPosFromBegin - CACHE_BUFFER_SIZE + 1); 
     int nLength = (int)Math.Min(CACHE_BUFFER_SIZE, sr.BaseStream.Length - ncachestartbuffer); 
     cachebuffer = new char[nLength]; 
     sr.DiscardBufferedData(); 
     sr.BaseStream.Seek(ncachestartbuffer, SeekOrigin.Begin); 
     sr.Read(cachebuffer, (int)0, (int)nLength); 

     return BufferedGetCharBackwards(sr, iPosFromBegin); 
    } 

Nota: -

  1. ReadLastLines llamada con nLineFrom comenzando en 0 para la última línea y nNoLines como el número de líneas a leer de nuevo desde.
  2. Invierte la lista, por lo que la primera es la última línea del archivo.
  3. bMore devuelve verdadero si hay más líneas para leer.
  4. Almacena en caché los datos en 1024 bloques de caracteres, por lo que es rápido, es posible que desee aumentar este tamaño para archivos muy grandes.

Enjoy!

1

Tuve problemas con el código. Esta es mi versión. Dado que es un archivo de registro, es posible que algo le esté escribiendo, por lo que es mejor asegurarse de que no lo está bloqueando.

Se llega al final. Comience a leer hacia atrás hasta llegar a n líneas. Luego lee todo a partir de ahí.

 int n = 5; //or any arbitrary number 
     int count = 0; 
     string content; 
     byte[] buffer = new byte[1]; 

     using (FileStream fs = new FileStream("text.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 
     { 
      // read to the end. 
      fs.Seek(0, SeekOrigin.End); 

      // read backwards 'n' lines 
      while (count < n) 
      { 
       fs.Seek(-1, SeekOrigin.Current); 
       fs.Read(buffer, 0, 1); 
       if (buffer[0] == '\n') 
       { 
        count++; 
       } 

       fs.Seek(-1, SeekOrigin.Current); // fs.Read(...) advances the position, so we need to go back again 
      } 
      fs.Seek(1, SeekOrigin.Current); // go past the last '\n' 

      // read the last n lines 
      using (StreamReader sr = new StreamReader(fs)) 
      { 
       content = sr.ReadToEnd(); 
      } 
     } 
0

Esto es de ninguna manera óptima, pero para un rápido control y sucio con los archivos de registro pequeñas que he estado usando algo como esto:

List<string> mostRecentLines = File.ReadLines(filePath) 
    // .Where(....) 
    // .Distinct() 
    .Reverse() 
    .Take(10) 
    .ToList() 
Cuestiones relacionadas