在C#中,将大文件加载到winform richtextbox中

本文关键字:加载 winform 文件 richtextbox | 更新日期: 2023-09-27 18:00:56

我需要将-10MB范围的文本文件加载到Winform RichTextBox中,但我当前的代码正在冻结UI。我试着让一个后台工作人员来加载,但似乎效果也不太好。

这是我尝试过的几个加载代码。有什么方法可以提高它的性能吗?谢谢

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

在C#中,将大文件加载到winform richtextbox中

关于如何调用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)));
        }
    }
}

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

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

为什么要拆分行并重新连接它们?

字符串为immutable,表示无法更改。所以每次执行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();

您可以看到字符串与StringBuilder

StringBuilder的结构是字符列表。StringBuilder也是Muttable。手段可以改变。

在循环内部,您可以使用字符串执行任何额外的任务,并将最终结果添加到StringBuilder中。循环结束后,StringBuilder终于准备好了。您必须将其转换为字符串并将其放入文本中。

我花了一段时间才搞定这个。。

测试一个&二:

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

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.

结论

  • 加载包含链接的文本需要10多倍的时间,在此期间,UI会冻结。

  • 加载10-15MB的文本数据本身并不是真正的问题。

测试三:

string bigFile = File.ReadAllText("D:''AllDVDFiles.txt");
richTextBox1.AppendText(bigFile);

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

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

乍一看,原因是一些文件名中的有趣字符。。将文件保存到UTF8并将读取命令更改为..:之后

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

正如预期的那样,文件在1-2秒内加载得很好。。

最终结论:

  • 您需要注意错误的编码,因为这些字符可能会在加载过程中冻结RTB
  • 而且,当您添加链接时,您必须期望加载比纯文本长很多(10-20x(。我试图通过准备一个Rtf字符串来欺骗RTB,但无济于事。似乎分析和存储所有这些链接总是需要很长时间

所以:如果你真的需要在每一行上都有一个链接,那么就把数据划分成更小的部分,并给用户一个滚动的界面;搜索那些部分。

当然,将所有这些行一行接一行地追加总是太慢了,但这已经在评论和其他答案中提到了。