如何读取日志文件的最后“n”行

需要一段代码来读出日志文件的最后“n行”。 我从网上得到了以下代码。我是C sharp的新手。 由于日志文件可能非常大,我想避免读取整个文件的开销。有人建议任何性能增强。 我真的不想读每个角色并改变位置。

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

您的代码执行得非常糟糕,因为您不允许任何缓存发生。
此外,它根本不适用于Unicode。

我写了以下实现:

 ///Returns the end of a text reader. ///The reader to read from. ///The number of lines to return. ///The last lneCount lines from the reader. public static string[] Tail(this TextReader reader, int lineCount) { var buffer = new List(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; } 

我的一个朋友使用这种方法 ( BackwardReader可以在这里找到):

 public static IList GetLogTail(string logname, string numrows) { int lineCnt = 1; List lines = new List(); 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; } 

这是我的答案: –

  private string StatisticsFile = @"c:\yourfilename.txt"; // Read last lines of a file.... public IList 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 strSort = new List(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(); } 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); } 

注意:-

  1. 使用nLineFrom调用ReadLastLines,从最后一行开始为0,将nNoLines作为要读回的行数。
  2. 它会反转列表,因此第一行是文件中的最后一行。
  3. 如果要读取更多行,则bMore返回true。
  4. 它将数据缓存在1024个char块中 – 因此速度很快,您可能希望为非常大的文件增加此大小。

请享用!

你的代码有问题。 这是我的版本。 由于它是一个日志文件,有些东西可能会写入它,所以最好确保你没有锁定它。

你走到最后。 开始向后阅读,直到达到n行。 然后从那里读取所有内容。

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

您现在可以在C#4.0中轻松完成的任务(在早期版本中只需要一点点努力)就是使用内存映射文件进行此类操作。 它是大文件的理想选择,因为您只能映射文件的一部分,然后将其作为虚拟内存访问。

这里有一个很好的例子 。

您的日志是否有类似长度的行? 如果是,则可以计算线的平均长度,然后执行以下操作:

  1. 寻求end_of_file – lines_needed * avg_line_length(previous_point)
  2. 阅读所有内容直到最后
  3. 如果你抓住足够的线,那没关系。 如果不是,请寻找previous_point – lines_needed * avg_line_length
  4. 读取到previous_point的所有内容
  5. 转到3

内存映射文件也是一个很好的方法 – 映射文件的尾部,计算行,映射前一个块,计算行等,直到你得到所需的行数

这绝不是最佳的,但对于使用小日志文件进行快速和脏检查我一直在使用这样的东西:

 List mostRecentLines = File.ReadLines(filePath) // .Where(....) // .Distinct() .Reverse() .Take(10) .ToList()