在C#中,将大文件加载到Winform RichTextBox

4

我需要将一个 10MB 的文本文件加载到 Winform RichTextBox 中,但是我的当前代码会导致用户界面冻结。我试过使用后台工作者加载,但效果并不好。

以下是我尝试的几种加载代码。有没有什么方法可以提高它的性能?谢谢。

    private BackgroundWorker bw1;
    private string[] lines;
    Action showMethod;
    private void button1_Click(object sender, EventArgs e)
    {
        bw1 = new BackgroundWorker();
        bw1.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw1.RunWorkerCompleted += bw_RunWorkerCompleted;
        string path = @"F:\DXHyperlink\Book.txt";
        if (File.Exists(path))
        {
            string readText = File.ReadAllText(path);
            lines = readText.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            bw1.RunWorkerAsync();

        }
    }

    private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        Invoke((ThreadStart)delegate()
        {
            for (int i = 0; i < lines.Length; i++)
            {
                richEditControl1.Text += lines[i] + "\n";
            }
        });
    }

我也尝试:

Action showMethod = delegate()
            {
                for (int i = 0; i < lines.Length; i++)
            {
                richEditControl1.Text += lines[i] + "\n";
            }
            };

为什么你要逐行添加文本?这样做肯定比一次性设置文本或 RTF 要慢得多! - TaW
因为我想要在每一行上创建特定的超链接。 - user3987052
至少使用 AppendText 而不是将文本连接到先前的文本。 - Steve
1
那是一个高尚的事业(即使你隐藏得这么好,我仍然看不到你在做它),但没有理由用那种方式来做。在添加到 RTB 之前,请先准备好一切!为了准备工作,请使用 StringBuilder!! - TaW
重新考虑你的用户界面。没有人能够在文本框中使用10兆字节的文本。围绕用户想要实现的目标重新设计您的用户界面。 - Dour High Arch
4个回答

2

这涉及到你如何调用UI更新,检查以下AppendText

private BackgroundWorker bw1;

private void button1_Click(object sender, EventArgs e)
{
    bw1 = new BackgroundWorker();
    bw1.DoWork += new DoWorkEventHandler(bw_DoWork);
    bw1.RunWorkerCompleted += bw_RunWorkerCompleted;
    bw1.RunWorkerAsync();
}

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    string path = @"F:\DXHyperlink\Book.txt";
    if (File.Exists(path))
    {
        string readText = File.ReadAllText(path);
        foreach (string line in readText.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
        {
            AppendText(line);
            Thread.Sleep(500);
        }
    }
}

private void AppendText(string line)
{
    if (richTextBox1.InvokeRequired)
    {
        richTextBox1.Invoke((ThreadStart)(() => AppendText(line)));
    }
    else
    {
        richTextBox1.AppendText(line + Environment.NewLine);
    }
}

除此之外,整个文件的阅读非常低效。我更愿意逐块地读取并更新UI。例如:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    string path = @"F:\DXHyperlink\Book.txt";
    const int chunkSize = 1024;
    using (var file = File.OpenRead(path))
    {
        var buffer = new byte[chunkSize];
        while ((file.Read(buffer, 0, buffer.Length)) > 0)
        {
            string stringData = System.Text.Encoding.UTF8.GetString(buffer);

            AppendText(string.Join(Environment.NewLine, stringData.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)));
        }
    }
}

我想对你的协作支持表示衷心的感谢,再次感谢你的帮助。 - user3987052

1

我花了一些时间才解决这个问题...

测试一和测试二:

首先,我创建了一些干净的数据:

string l10 = " 123456789";
string l100 = l10 + l10 + l10 + l10 + l10 + l10 + l10 + l10 +l10 + l10;
string big = "";
StringBuilder sb = new StringBuilder(10001000);
for (int i = 1; i <= 100000; i++)
    // this takes 3 seconds to load
    sb.AppendLine(i.ToString("Line 000,000,000 ") + l100 + " www-stackexchange-com "); 
     // this takes 45 seconds to load !!
    //sb.AppendLine(i.ToString("Line 000,000,000 ") + l100 + " www.stackexchange.com "); 
big = sb.ToString();

Console.WriteLine("\r\nStringLength: " + big.Length.ToString("###,###,##0") + "  ");

richTextBox1.WordWrap = false;
richTextBox1.Font = new System.Drawing.Font("Consolas", 8f);
richTextBox1.AppendText(big);
Console.WriteLine(richTextBox1.Text.Length.ToString("###,###,##0") + " chars in RTB");
Console.WriteLine(richTextBox1.Lines.Length.ToString("###,###,##0") + " lines in RTB ");

显示大约14MB的100k行需要2-3秒或45-50秒。将行数增加到500k行会将正常文本加载时间提高到约15-20秒,并且包含每行末尾的(有效)链接版本需要几分钟。
当我达到1M行时,加载会使VS崩溃。
结论: 1. 带有链接的文本加载需要10倍以上的时间,此期间UI会冻结。 2. 加载10-15MB的文本数据并不是真正的问题。
测试三:
string bigFile = File.ReadAllText("D:\\AllDVDFiles.txt");
richTextBox1.AppendText(bigFile);

(这实际上是我调查的开始..)它试图加载一个8 MB大小的文件,其中包含来自大量数据DVD的目录和文件信息。而且:它冻结了。

正如我们所看到的,文件大小不是原因。也没有嵌入任何链接。

从第一眼看来,原因是某些文件名中有趣的字符.. 将文件保存为UTF8并将读取命令更改为..:

string bigFile = File.ReadAllText("D:\\AllDVDFiles.txt", Encoding.UTF8);

...文件在1-2秒内正常加载,与预期相符...

最终结论:

  • 必须注意错误的编码,因为这些字符会在加载期间冻结 RTB。
  • 当添加链接时,您必须预计加载时间会比纯文本长很多(10-20倍)。我试图通过准备 Rtf 字符串来欺骗 RTB,但是毫无帮助。似乎分析和存储所有这些链接将始终需要如此长的时间。

所以:如果您确实需要每行都有一个链接,请将数据划分为较小的部分,并为用户提供一个界面以滚动和搜索这些部分。

当然,一行一行地附加所有这些行将始终过于缓慢,但这已经在评论和其他答案中提到了。


我想对你们紧密的支持表示衷心的感谢。 - user3987052

1

您不希望在循环中连接字符串。

System.String对象是不可变的。当两个字符串被连接时,会创建一个新的String对象。迭代字符串连接会创建多个未引用的字符串,必须进行垃圾回收。为了获得更好的性能,请使用System.Text.StringBuilder类。

以下代码非常低效:

for (int i = 0; i < lines.Length; i++)
{
    richEditControl1.Text += lines[i] + "\n";
}

尝试改为:

改为:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    // Cpu intensive work happens in the background thread.
    var lines = string.Join("\r\n", lines);

    // The following code is invoked in the UI thread and it only assigns the result.
    // So that the UI is not blocked for long.
    Invoke((ThreadStart)delegate()
    {
        richEditControl1.Text = lines;
    });
}

它不能解决我的问题。它的工作方式与我的代码相同。 - user3987052
@AhmerAliAhsan 当文本加载到richtextbox中时,UI是否开始正常工作,还是仍然不太响应? - Dzienny
无论选择哪种解决方案,都将richEditControl1.Text += s替换为richEditControl1. **AppendText(s)**。 - Graffito
我想对你们协调的支持表示非常感激。 - user3987052

1

为什么要分割行并重新连接?

字符串是不可变的,这意味着不能被更改。所以每次执行Text+= "..."时,它都必须创建新的字符串并将其放入Text中。因此,对于10 mb的字符串,这不是理想的方式,处理巨大字符串可能需要几个世纪的时间。

您可以查看C#中可变和不可变字符串之间的区别是什么?

如果您真的想要分割它们并重新连接它们,那么StringBuilder是正确的选择。

        StringBuilder strb = new StringBuilder();

        for (int i = 0; i < lines.Length; i++)
        {
            strb.Append(lines[i] + "\n");
        }

        richEditControl1.Text = strb.ToString();

你可以看到String vs. StringBuilder StringBuilder的结构是字符列表。此外,StringBuilder是可变的,意味着可以更改。
在循环内部,您可以对字符串执行任何额外任务,并将最终结果添加到StringBuilder中。最后,在循环之后,您的StringBuilder已经准备好了。您需要将其转换为字符串并放入文本中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接