用C#连续读取文件

问题描述 投票:0回答:7

我想像 GNU tail 一样使用“-f”参数连续读取文件。我需要它来实时读取日志文件。 正确的做法是什么?

c# file-io system.io.file
7个回答
44
投票

更自然的使用方法

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
仅用于唤醒主读取周期。


40
投票

您想以二进制模式打开

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
方法。您可以通过在程序启动时仅打开一次文件来进行一些优化。这取决于你。


6
投票

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

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 部分更改为不同的计时,还可以将其与文件观察器和阻塞模式 - Monitor.Enter/Wait/Pulse 链接。对我来说,计时器就足够了,如果文件没有改变,它最多每秒检查一次文件长度。


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

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

    foreach (string line in TailFrom(file)) 
    {
        Console.WriteLine($"line read= {line}");            
    }

2
投票

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


0
投票
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);  
}

-10
投票

如果您只是在寻找一个工具来执行此操作,请查看免费版本的Bare tail

© www.soinside.com 2019 - 2024. All rights reserved.