2011-01-06 14 views
13

potrzebują fragmentu kodu, który odczytałby ostatnie "n linii" pliku dziennika. Wymyśliłem następujący kod z sieci. Jestem trochę nowy w C-sie. Ponieważ plik dziennika może być dość duży, chcę uniknąć narzutu na czytanie całego pliku. Może ktoś sugeruje jakiekolwiek ulepszenie wydajności. I tak naprawdę nie chcę czytać każdego znaku i zmieniać pozycji.Jak odczytać ostatnie "n" wiersze pliku logu

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

nie można używać StreamReader tak. – SLaks

+0

spójrz na http://stackoverflow.com/questions/1271225/c-reading-a-file-line-by-line. Możesz wtedy użyć rozszerzenia LINQ '.Last()' na IEnumerable, aby uzyskać ostatnie N linii –

+0

@Russ: Nie, nie możesz. LINQ nie może wydajnie dać ci ostatnich _n_ linii. – SLaks

Odpowiedz

9

Twój kod będzie działać bardzo źle, ponieważ nie pozwalasz na buforowanie.
Ponadto nie zadziała dla wszystkich dla Unicode.

napisałem następujący realizacji:

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

bardzo spodobała się idea zmieniającego się bufora. Ale nie będzie to skutecznie przeczytać cały plik dziennika. Czy istnieje skuteczny sposób "szukania" na początku n-tej linii.i robienia odczytuLine() stamtąd. To może być głupia wątpliwość !! – frictionlesspulley

+2

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

0

coś, co można teraz zrobić bardzo łatwo w języku C# 4.0 (i tylko z odrobiną wysiłku we wcześniejszych wersjach) jest wykorzystanie pamięci mapowane plików dla tego typu operacja. Jest idealny do dużych plików, ponieważ można odwzorować tylko część pliku, a następnie uzyskać do niego dostęp jako pamięć wirtualną.

Istnieje good example here.

+0

To jest dobry pomysł, jednak o ile rozumiem, nie pozwala na czytanie plików po liniach (tekstach), o które pyta. – AaA

4

Mój znajomy używa this method (BackwardReader można znaleźć 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

** Dlaczego ** to 'numrows' string? – SLaks

+0

To samo pytanie co SLaks, ale +1 dla 'BackwardReader'. Nie wiedziałem o tym. – BrunoLM

+0

Będę szczery, SLAKS, nie mogę znaleźć niczego na blogu mojego kolegi, który wyjaśnia dlaczego. Widzę, że jest to zasadniczo metoda WCF wywoływana z JavaScript, ale nie jestem pewien, czy to odpowiednio wyjaśnia. –

0

Czy Twój dziennik mieć linie podobnej długości? Jeśli tak, to można obliczyć średnią długość linii, a następnie wykonaj następujące czynności:

  1. dążyć do END_OF_FILE - lines_needed * avg_line_length (previous_point)
  2. czytać wszystko do końca
  3. jeśli wystarczająco chwycił linie, w porządku. Jeżeli nie, należy dążyć do previous_point - lines_needed * avg_line_length
  4. czytać wszystko do previous_point
  5. goto 3

plik pamięci odwzorowany to również dobry sposób - mapa ogon pliku, obliczyć linie, mapy poprzedni blok, oblicz linie itd., aż pojawi się liczba linii potrzebne

2

Oto moja odpowiedź: -

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

Uwaga: -

  1. Wywołanie ReadLastLines z nLineFrom począwszy od 0 dla ostatniej linii i nNoLines jako liczba linii do odczytania z powrotem.
  2. Powoduje odwrócenie listy, więc pierwsza jest ostatnią linią w pliku.
  3. bWięcej zwraca wartość true, jeśli jest więcej linii do odczytania.
  4. Przechowuje dane w pamięci w 1024 kawałkach - więc jest szybki, możesz zwiększyć ten rozmiar w przypadku bardzo dużych plików.

Ciesz się!

1

Wystąpił problem z kodem. To jest moja wersja. Ponieważ jest to plik dziennika, coś może na niego pisać, więc najlepiej upewnij się, że go nie blokujesz.

Idziesz do końca. Zacznij czytać wstecz, aż dotrzesz do n linii. Następnie przeczytaj wszystko od tego miejsca.

 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

To jest w żaden sposób optymalny, ale na szybkie i brudne kontroli z małych plików dziennika Używam coś takiego:

List<string> mostRecentLines = File.ReadLines(filePath) 
    // .Where(....) 
    // .Distinct() 
    .Reverse() 
    .Take(10) 
    .ToList() 
Powiązane problemy