简体   繁体   English

C#连续读取文件

[英]c# continuously read file

I want to read file continuously like GNU tail with "-f" param. 我想像带有“ -f”参数的GNU尾部一样连续读取文件。 I need it to live-read log file. 我需要它来实时读取日志文件。 What is the right way to do it? 正确的做法是什么?

You want to open a FileStream in binary mode. 您要以二进制模式打开FileStream Periodically, seek to the end of the file minus 1024 bytes (or whatever), then read to the end and output. 定期查找文件的末尾减去1024个字节(或其他任何字节),然后读取到末尾并输出。 That's how tail -f works. 这就是tail -f工作方式。

Answers to your questions: 您的问题的答案:

Binary because it's difficult to randomly access the file if you're reading it as text. 二进制文件,因为如果您以文本形式读取文件,则很难随机访问该文件。 You have to do the binary-to-text conversion yourself, but it's not difficult. 您必须自己进行二进制到文本的转换,但这并不困难。 (See below) (见下文)

1024 bytes because it's a nice convenient number, and should handle 10 or 15 lines of text. 1024字节,因为它是一个非常方便的数字,并且应处理10或15行文本。 Usually. 通常。

Here's an example of opening the file, reading the last 1024 bytes, and converting it to text: 这是打开文件,读取最后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);
    }
}

Note that you must open with FileShare.ReadWrite , since you're trying to read a file that's currently open for writing by another process. 请注意,您必须使用FileShare.ReadWrite打开,因为您正在尝试读取当前打开的文件,以供其他进程进行写入。

Also note that I used Encoding.Default , which in US/English and for most Western European languages will be an 8-bit character encoding. 还要注意,我使用了Encoding.Default ,它在美国/英语和大多数西欧语言中将是8位字符编码。 If the file is written in some other encoding (like UTF-8 or other Unicode encoding), It's possible that the bytes won't convert correctly to characters. 如果文件是以其他某种编码(例如UTF-8或其他Unicode编码)编写的,则字节可能无法正确转换为字符。 You'll have to handle that by determining the encoding if you think this will be a problem. 如果您认为这将是一个问题,则必须通过确定编码来解决。 Search Stack overflow for info about determining a file's text encoding. 搜索堆栈溢出以获取有关确定文件的文本编码的信息。

If you want to do this periodically (every 15 seconds, for example), you can set up a timer that calls the ReadTail method as often as you want. 如果要定期执行此操作(例如,每15秒执行一次),则可以设置一个计时器,该计时器可以根据需要ReadTail调用ReadTail方法。 You could optimize things a bit by opening the file only once at the start of the program. 您可以通过在程序启动时仅打开一次文件来稍微优化一下。 That's up to you. 随你(由你决定。

More natural approach of using FileSystemWatcher : 使用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();

Here the main reading cycle stops to wait for incoming data and FileSystemWatcher is used just to awake the main reading cycle. 在这里,主读取周期停止以等待传入数据,而FileSystemWatcher仅用于唤醒主读取周期。

To continuously monitor the tail of the file, you just need to remember the length of the file before. 要连续监视文件的尾部,您只需要记住文件的长度即可。

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

I had to use ASCIIEncoding, because this code isn't smart enough to cater for variable character lengths of UTF8 on buffer boundaries. 我必须使用ASCIIEncoding,因为此代码不够灵巧,无法满足缓冲区边界上UTF8的可变字符长度。

Note: You can change the Thread.Sleep part to be different timings, and you can also link it with a filewatcher and blocking pattern - Monitor.Enter/Wait/Pulse. 注意:您可以将Thread.Sleep部分更改为不同的时间,还可以将其与文件监视程序和阻止模式链接-Monitor.Enter / Wait / Pulse。 For me the timer is enough, and at most it only checks the file length every second, if the file hasn't changed. 对我来说,计时器就足够了,如果文件没有更改,它最多只能每秒检查一次文件长度。

This is my solution 这是我的解决方案

    static IEnumerable<string> 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);
            }
        }
    }

so, in your code you can do 因此,在您的代码中,您可以执行

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

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

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM