在 C# 中,将大文件加载到 winform richtextbox
In C#, Loading large file into winform richtextbox
我需要将一个 - 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";
}
};
You don't want to concatenate strings in a loop.
A System.String object is immutable. When two strings are
concatenated, a new String object is created. Iterative string
concatenation creates multiple strings that are un-referenced and must
be garbage collected. For better performance, use the
System.Text.StringBuilder class.
下面的代码效率很低:
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+= "..."
时,它都必须创建新字符串并将其放入文本中。因此,对于 10 mb 的字符串,它不是理想的方法,可能需要几个世纪才能完成巨大字符串的此类任务。
可以看到What is the difference between a mutable and immutable string in 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 准备好了。您必须将其转换为字符串并将其放入文本中。
这是关于您如何调用 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)));
}
}
}
我花了一段时间才搞定这个..
测试一和二:
首先我创建了一些干净的数据:
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 的 10 万行需要 2-3 秒或 45-50 秒。
将行数增加到 500k 行会使正常文本加载时间增加到大约 15-20 秒,而在每行末尾包含(有效)link 的版本需要几分钟。
当我转到 1M 行时加载崩溃 VS。
结论:
加载包含 link 的文本需要 10 倍以上的时间,在此期间 UI 会冻结。
加载 10-15MB 的文本数据本身并不是真正的问题。
测试三:
string bigFile = File.ReadAllText("D:\AllDVDFiles.txt");
richTextBox1.AppendText(bigFile);
(这实际上是我调查的开始..)这试图加载一个 8 MB 的大文件,其中包含来自大量数据 DVD 的目录和文件信息。并且:它也 冻结。
正如我们所见,文件大小不是原因。也没有嵌入任何 link。
乍一看,原因是某些文件名中有有趣的字符。将文件保存为 UTF8 并将读取命令更改为...后:
string bigFile = File.ReadAllText("D:\AllDVDFiles.txt", Encoding.UTF8);
..正如预期的那样,文件在 1-2 秒内加载得很好..
最终结论:
- 您需要注意错误的编码,因为这些字符可能会在加载过程中冻结 RTB。
- 而且,当您添加 links 时,您必须预计加载时间会 长很多(10-20 倍) 比纯文本。我试图通过准备一个 Rtf 字符串来欺骗 RTB,但它没有帮助。似乎分析和存储所有这些 link 总是需要很长时间。
所以:如果你真的需要在每一行都使用 link,请将数据 划分 为更小的部分,并为用户提供一个界面来滚动和搜索这些部分部分。
当然,将所有这些行一一添加总是太慢了,但这已经在评论和其他答案中提到了。
我需要将一个 - 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";
}
};
You don't want to concatenate strings in a loop.
A System.String object is immutable. When two strings are concatenated, a new String object is created. Iterative string concatenation creates multiple strings that are un-referenced and must be garbage collected. For better performance, use the System.Text.StringBuilder class.
下面的代码效率很低:
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+= "..."
时,它都必须创建新字符串并将其放入文本中。因此,对于 10 mb 的字符串,它不是理想的方法,可能需要几个世纪才能完成巨大字符串的此类任务。
可以看到What is the difference between a mutable and immutable string in 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 准备好了。您必须将其转换为字符串并将其放入文本中。
这是关于您如何调用 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)));
}
}
}
我花了一段时间才搞定这个..
测试一和二:
首先我创建了一些干净的数据:
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 的 10 万行需要 2-3 秒或 45-50 秒。
将行数增加到 500k 行会使正常文本加载时间增加到大约 15-20 秒,而在每行末尾包含(有效)link 的版本需要几分钟。
当我转到 1M 行时加载崩溃 VS。
结论:
加载包含 link 的文本需要 10 倍以上的时间,在此期间 UI 会冻结。
加载 10-15MB 的文本数据本身并不是真正的问题。
测试三:
string bigFile = File.ReadAllText("D:\AllDVDFiles.txt");
richTextBox1.AppendText(bigFile);
(这实际上是我调查的开始..)这试图加载一个 8 MB 的大文件,其中包含来自大量数据 DVD 的目录和文件信息。并且:它也 冻结。
正如我们所见,文件大小不是原因。也没有嵌入任何 link。
乍一看,原因是某些文件名中有有趣的字符。将文件保存为 UTF8 并将读取命令更改为...后:
string bigFile = File.ReadAllText("D:\AllDVDFiles.txt", Encoding.UTF8);
..正如预期的那样,文件在 1-2 秒内加载得很好..
最终结论:
- 您需要注意错误的编码,因为这些字符可能会在加载过程中冻结 RTB。
- 而且,当您添加 links 时,您必须预计加载时间会 长很多(10-20 倍) 比纯文本。我试图通过准备一个 Rtf 字符串来欺骗 RTB,但它没有帮助。似乎分析和存储所有这些 link 总是需要很长时间。
所以:如果你真的需要在每一行都使用 link,请将数据 划分 为更小的部分,并为用户提供一个界面来滚动和搜索这些部分部分。
当然,将所有这些行一一添加总是太慢了,但这已经在评论和其他答案中提到了。