c#连续读取文件

我想像GNU尾部一样连续读取文件“-f”param。 我需要它来实时读取日志文件。 这样做的正确方法是什么?

您想以二进制模式打开FileStream 。 定期查找文件末尾减去1024字节(或其他),然后读到结尾并输出。 这就是tail -f工作方式。

您的问题的答案:

二进制文件,因为如果您将文件作为文本阅读,则很难随机访问该文件。 您必须自己进行二进制到文本转换,但这并不困难。 (见下文)

1024字节,因为它是一个很方便的数字,应该处理10或15行文本。 通常。

这是打开文件,读取最后1024个字节并将其转换为文本的示例:

 static void ReadTail(string filename) { using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { // Seek 1024 bytes from the end of the file fs.Seek(-1024, SeekOrigin.End); // read 1024 bytes byte[] bytes = new byte[1024]; fs.Read(bytes, 0, 1024); // Convert bytes to string string s = Encoding.Default.GetString(bytes); // or string s = Encoding.UTF8.GetString(bytes); // and output to console Console.WriteLine(s); } } 

请注意,您必须使用FileShare.ReadWrite打开,因为您正在尝试读取当前打开以供另一个进程写入的文件。

另请注意,我使用的是Encoding.Default ,它在美国/英语和大多数西欧语言中都是8位字符编码。 如果文件是用其他编码(如UTF-8或其他Unicode编码)编写的,则字节可能无法正确转换为字符。 如果您认为这将是一个问题,您将必须通过确定编码来处理。 搜索堆栈溢出以获取有关确定文件文本编码的信息。

如果您想定期执行此操作(例如,每隔15秒),您可以设置一个根据需要ReadTail调用ReadTail方法的计时器。 您可以通过在程序开始时仅打开文件一次来优化一些事情。 随你(由你决定。

使用FileSystemWatcher更自然的方法:

  var wh = new AutoResetEvent(false); var fsw = new FileSystemWatcher("."); fsw.Filter = "file-to-read"; fsw.EnableRaisingEvents = true; fsw.Changed += (s,e) => wh.Set(); var fs = new FileStream("file-to-read", FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using (var sr = new StreamReader(fs)) { var s = ""; while (true) { s = sr.ReadLine(); if (s != null) Console.WriteLine(s); else wh.WaitOne(1000); } } wh.Close(); 

这里主读取周期停止等待输入数据, FileSystemWatcher仅用于唤醒主读取周期。

要连续监视文件的尾部,您只需要记住之前文件的长度。

 public static void MonitorTailOfFile(string filePath) { var initialFileSize = new FileInfo(filePath).Length; var lastReadLength = initialFileSize - 1024; if (lastReadLength < 0) lastReadLength = 0; while (true) { try { var fileSize = new FileInfo(filePath).Length; if (fileSize > lastReadLength) { using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { fs.Seek(lastReadLength, SeekOrigin.Begin); var buffer = new byte[1024]; while (true) { var bytesRead = fs.Read(buffer, 0, buffer.Length); lastReadLength += bytesRead; if (bytesRead == 0) break; var text = ASCIIEncoding.ASCII.GetString(buffer, 0, bytesRead); Console.Write(text); } } } } catch { } Thread.Sleep(1000); } } 

我不得不使用ASCIIEncoding,因为这段代码不够智能,无法满足缓冲区边界上UTF8的可变字符长度。

注意:您可以将Thread.Sleep部分更改为不同的时序,还可以将其与filewatcher和阻塞模式 – Monitor.Enter / Wait / Pulse相关联。 对我来说,计时器足够了,如果文件没有改变,它最多只会每秒检查一次文件长度。

您可以使用FileSystemWatcher类,它可以发送文件系统上发生的不同事件的通知,例如更改的文件。

这是我的解决方案

  static IEnumerable TailFrom(string file) { using (var reader = File.OpenText(file)) { while (true) { string line = reader.ReadLine(); if (reader.BaseStream.Length < reader.BaseStream.Position) reader.BaseStream.Seek(0, SeekOrigin.Begin); if (line != null) yield return line; else Thread.Sleep(500); } } } 

所以,在你的代码中你可以做到

  foreach (string line in TailFrom(file)) { Console.WriteLine($"line read= {line}"); } 
 private void button1_Click(object sender, EventArgs e) { if (folderBrowserDialog.ShowDialog() == DialogResult.OK) { path = folderBrowserDialog.SelectedPath; fileSystemWatcher.Path = path; string[] str = Directory.GetFiles(path); string line; fs = new FileStream(str[0], FileMode.Open, FileAccess.Read, FileShare.ReadWrite); tr = new StreamReader(fs); while ((line = tr.ReadLine()) != null) { listBox.Items.Add(line); } } } private void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e) { string line; line = tr.ReadLine(); listBox.Items.Add(line); } 

如果你只是想找一个工具来做这个,那么看看裸尾的免费版本