2011-01-06 18 views
13

necessario uno snippet di codice che possa leggere le ultime "n righe" di un file di registro. Sono venuto con il seguente codice dalla rete. Sono un po 'nuovo per C sharp. Poiché il file di registro potrebbe essere piuttosto grande, voglio evitare di sovraccaricare la lettura dell'intero file. Qualcuno suggerisce un miglioramento delle prestazioni. I non voglio veramente leggere ogni personaggio e cambiare posizione.Come leggere le ultime righe "n" del file di 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

Non è possibile utilizzare uno StreamReader del genere. – SLaks

+0

dai uno sguardo a http://stackoverflow.com/questions/1271225/c-reading-a-file-line-by-line. È quindi possibile utilizzare l'estensione LINQ '.Last()' su IEnumerable per ottenere le ultime N righe –

+0

@Russ: No, non è possibile. LINQ non può fornire in modo efficiente le ultime righe _n_. – SLaks

risposta

9

Il codice funzionerà molto male, dal momento che non consente il verificarsi di alcuna memorizzazione nella cache.
Inoltre, non funzionerà per tutto per Unicode.

ho scritto la seguente implementazione:

///<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

è piaciuto molto l'idea del buffer di spostamento. Ma questo non leggerà efficacemente l'intero file di registro. C'è un modo efficace per "cercare" all'inizio della linea n.e fare un readLine() da lì. Questo potrebbe essere uno stupido dubbio sul mio !! – frictionlesspulley

+2

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

0

Qualcosa che ora è possibile fare molto facilmente in C# 4.0 (e con appena un po 'di sforzo nelle versioni precedenti) è la memoria utilizzo mappata file per questo tipo di operazione. È ideale per file di grandi dimensioni perché è possibile mappare solo una parte del file, quindi accedervi come memoria virtuale.

C'è uno good example here.

+0

Questa è una buona idea, tuttavia per quanto ho capito, non permette di leggere i file per linee (testo) come la domanda sta chiedendo. – AaA

4

Un mio amico usa this method (BackwardReader si possono trovare 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

** PERCHÉ ** è 'numrows' una stringa? – SLaks

+0

Stessa domanda di SLaks, ma +1 per 'BackwardReader'. Non lo sapevo. – BrunoLM

+0

Sarò onesto, SLaks, non riesco a trovare nulla nel post del blog del mio amico che spieghi perché. Vedo che si tratta essenzialmente di un metodo WCF chiamato da JavaScript, ma non sono sicuro che ciò lo spieghi adeguatamente. –

0

Il vostro registro ha righe di lunghezza simile? Se sì, allora si può calcolare la lunghezza media della linea, quindi effettuare le seguenti operazioni:

  1. cercano di end_of_file - lines_needed * avg_line_length (previous_point)
  2. leggere tutto fino alla fine
  3. se si ha afferrato abbastanza linee, va bene. Se no, cercare di previous_point - lines_needed * avg_line_length
  4. leggere tutto fino a previous_point
  5. goto 3

file mappato in memoria è anche un buon metodo - mappare la coda di file di, calcolare le linee, mappa il blocco precedente, calcolare le linee ecc fino ad ottenere il numero di righe necessaria

2

Ecco la mia risposta: -

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. Chiama ReadLastLines con nLineDa iniziare da 0 per l'ultima riga e nNoLines come numero di righe da cui leggere.
  2. Inverte l'elenco in modo che il primo sia l'ultima riga del file.
  3. bMore restituisce true se sono presenti più righe da leggere.
  4. Memorizza nella cache i dati in 1024 blocchi di carbone - quindi è veloce, è possibile che si desideri aumentare questa dimensione per file di grandi dimensioni.

Divertiti!

1

Ha avuto problemi con il codice. Questa è la mia versione. Dato che è un file di log, potrebbe esserci qualcosa da scrivere, quindi è meglio assicurarti di non bloccarlo.

Si va fino alla fine. Inizia a leggere all'indietro finché non raggiungi n linee. Quindi leggi tutto da lì in poi.

 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

Questo è in alcun modo ottimale, ma per i controlli rapido e sporco con i file di log di piccole dimensioni ho usato qualcosa di simile:

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