c#连续读取文件

Dmy*_*nko 33 c# file-io

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

Jim*_*hel 29

您想要以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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • Tail不会盲目地获取最后1024个字节 - 它会跟踪最后一个大小和新大小,并且只获取最近添加的字节.http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/tail.c (8认同)
  • 值得注意的是,如果文件小于1024字节,代码将抛出IOException,因为尝试将文件指针移动到文件开始之前的某个点.为了避免问题:fs.Seek(Math.Max(-1024,-fs.Length),SeekOrigin.End); (2认同)

tsu*_*sul 27

更自然的使用方法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();
Run Code Online (Sandbox Code Playgroud)

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

  • 文件系统观察程序的一个警告 - 似乎在引发事件之前需要刷新文件。如果你想实时访问文件更改,你需要定期打开文件,然后将文件位置设置到文件末尾,这将强制刷新任何挂起的内存缓冲区,否则你就会心血来潮操作系统决定何时要刷新。通常这不是问题,除非您想立即进行任何更改。 (4认同)
  • 你必须做:`fsw.EnableRaisingEvents = true;` 在`fsw.Changed` 事件被触发之前 (2认同)

Tod*_*odd 7

要持续监控文件的尾部,只需要记住之前的文件长度即可。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

注意:您可以将 Thread.Sleep 部分更改为不同的时间,也可以将其与文件观察器和阻塞模式链接 - Monitor.Enter/Wait/Pulse。对我来说,计时器就足够了,如果文件没有更改,它最多只能每秒检查文件长度。

  • 大多数日志文件将以新行终止,您不需要关心 utf-8 编码,因此记住最后一个新行位置确实是您想要的 (2认同)

ioj*_*ode 5

这是我的解决方案:

    static IEnumerable<string> TailFrom(string file, bool onlyNewLines = false)
    {
        using (var reader = File.OpenText(file))
        {
            if (onlyNewLines) reader.BaseStream.Seek(0, SeekOrigin.End);
            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);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

因此,在您的代码中您可以执行以下操作:

    foreach (string line in TailFrom(file)) 
    {
        Console.WriteLine($"line read= {line}");            
    }
Run Code Online (Sandbox Code Playgroud)