簡體   English   中英

C#連續讀取文件

[英]c# continuously read file

我想像帶有“ -f”參數的GNU尾部一樣連續讀取文件。 我需要它來實時讀取日志文件。 正確的做法是什么?

您要以二進制模式打開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部分更改為不同的時間,還可以將其與文件監視程序和阻止模式鏈接-Monitor.Enter / Wait / Pulse。 對我來說,計時器就足夠了,如果文件沒有更改,它最多只能每秒檢查一次文件長度。

這是我的解決方案

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

因此,在您的代碼中,您可以執行

    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