【问题标题】:Reading text from a file has very slow performance, and I'd like to know why从文件中读取文本的性能非常慢,我想知道为什么
【发布时间】:2013-07-31 06:01:38
【问题描述】:

我并不是真正的程序员,正如您将在此处看到的那样,但如果能获得一些帮助以加快这个简单的搜索,我将不胜感激:

我有一些代码从 10 兆字节的文本文件中读取,并将相关文本填充到文本框,以帮助工作人员搜索零件号。它适用于后台工作人员,并且它填充文本框的速度非常慢,我想知道如何加快速度?可能是 String.Join 之类的东西?

 using (System.IO.StreamReader file = new System.IO.StreamReader(@"T:\\PARTS\\DATABASE\\PARTS.txt"))
        {
            while ((line = file.ReadLine()) != null)
            {
                if ((backgroundWorker1.CancellationPending == true))
                {
                    e.Cancel = true;
                }
                else if (line.Contains(partNumbersText.Text))
                {
                    Action action = () => matchesText.Text += (line + Environment.NewLine).ToString();
                    matchesText.Invoke(action); // Or use BeginInvoke


                }

            }
        }

感谢您的阅读

【问题讨论】:

  • 文件有多大,有多少内容要添加到文本框中?
  • 我很抱歉,我现在意识到这一点很重要:10megs 所以它可以很容易地被读取到内存中。该文件永远不会比这大得多。
  • 10MB 是 很多 个部件号。
  • 非常慢是否有一些时间单位的值?
  • @JoeEnos 我不认为这是包含,而是连接。运行一个 c# 程序,将 10MB 的文本天真地连接成一个字符串,然后看着它突然停止。

标签: c#


【解决方案1】:

如果它是一个大文件,您将希望使用StringBuilder 而不是串联,因为字符串在幕后是不可变的,因此一遍又一遍的串联变得非常昂贵。试试这样的:

using (System.IO.StreamReader file = new System.IO.StreamReader(@"T:\\PARTS\\DATABASE\\PARTS.txt"))
{
    StringBuilder strBlder = new StringBuilder();
    while ((line = file.ReadLine()) != null)
    {
        if ((backgroundWorker1.CancellationPending == true))
        {
            e.Cancel = true;
        }
        else if (line.Contains(partNumbersText.Text))
        {
           strBlder.Append(line + Environment.NewLine);
        }               
    }
    Action action = () => matchesText.Text = strBlder.ToString()
    matchesText.Invoke(action);
}

@Jim 的评论,如果您想按原样显示文本,您可以每 x 个条目打印一次,这样它会提高一些速度,但在看到任何内容之前不必阅读整个文件:

const int ITERATIONS_PER_UI_UPDATE = 20;
using (System.IO.StreamReader file = new System.IO.StreamReader(@"T:\\PARTS\\DATABASE\\PARTS.txt"))
{
    int count = 0;
    StringBuilder strBlder = new StringBuilder();
    while ((line = file.ReadLine()) != null)
    {
        if ((backgroundWorker1.CancellationPending == true))
        {
            e.Cancel = true;
        }
        else if (line.Contains(partNumbersText.Text))
        {
           strBlder.Append(line + Environment.NewLine);
        }   
        count++;
        if ((count % ITERATIONS_PER_UI_UPDATE) == 0))
        {
             Action action = () => matchesText.Text = strBlder.ToString()
             matchesText.Invoke(action);
        }     
    }
    Action action = () => matchesText.Text = strBlder.ToString()
    matchesText.Invoke(action);
}

【讨论】:

  • 这意味着他必须阅读整个文件才能看到结果。大多数搜索都不是这样工作的,它们会随着数据的到来而更新。
  • 另一方面,如果我们认为 10mb 是一个小文件,它实际上可以加快速度,那么谁在乎呢?
  • 谢谢大家的回答。这对我来说是最容易编译的。我玩了 20 次更新与 1000 次等,但是如果有意义的话,您提供的第一个代码示例似乎更快?我稍后再测试。 UI 在我运行时有点反应迟钝,我不确定如何修复,但搜索结果来得足够快,这不是一个大问题,但我也想理解这一点?再次感谢
【解决方案2】:

改变这个:

matchesText.Invoke(action);

到这里:

matchesText.BeginInvoke(action); //Not sure about the winforms syntax for this.

因为第一个会让你的 Backgroundworker 不必要地等待 UI 刷新,而第二个不会。

【讨论】:

    【解决方案3】:

    不要在每次得到结果时更新文本框。使用 StringBuilder 构建您的结果对象,并仅每隔一段时间更新一次文本框。使用 ReportProgress 机制也是一个好主意,如下所示:

    using (System.IO.StreamReader file = new System.IO.StreamReader(@"T:\\PARTS\\DATABASE\\PARTS.txt"))
    {
        var results = new StringBuilder();
        var nextUpdate = DateTime.Now.AddMilliseconds(500);
        while ((line = file.ReadLine()) != null)
        {
            if ((backgroundWorker1.CancellationPending == true))
            {
                e.Cancel = true;
                break;
            }
    
            if (line.Contains(partNumbersText.Text))
            {
                results.AppendLine(line);
            }
    
            if (DateTime.Now > nextUpdate)
            {
                nextUpdate = DateTime.Now.AddMilliseconds(500);
                backgroundWorker1.ReportProgress(0, results.ToString());
    
                //move this code to the ProgressChanged event
                //matchesText.Invoke(() => matchesText.Text = results.ToString()); // Or use 
            }
        }
    }
    

    此外,.Contains() 检查 10Mb 的磁盘数据听起来很昂贵。您可以通过将文件加载到内存中来加快速度。 10Mb 在现代系统上不算什么,只要您小心避免以在 .Net 大型对象堆上创建多个条目的方式重新加载该数据,这将是目前为止的路要走。

    【讨论】:

    • 谢谢,但我该搬到哪里去://matchesText.Invoke(() => matchesText.Text = results.ToString()); // 至?如果我把它留在原地,我会得到无法将 lambda 表达式转换为类型 system.delegate...我不确定进度更改事件在哪里?
    • 您可能还没有创建 ProgressChanged 事件。这是 BackgroundWorker 可以引发的事件。根据您的错误消息,您可能需要将 Action 调用保留为两个单独的行。
    【解决方案4】:

    你每次都在文件中搜索

    查看整个文件和contains 需要很长时间,您应该将文本加载到允许您搜索部件号的对象中,例如字典,但您确实说过它太大了,你仍然必须能够缓存一些数字,即使做这样的事情

    //If there was a way to extract the parts number from each line I would do this
    //but I don't know what the format is so I can't provide the code
    //cache is a Dictionary>
    
    if(!cache.ContainsKey(partsNumber.Text))
    {
    
    //then search through the file
    cache.Add(partsNumber.Text,new List());
    
    using (System.IO.StreamReader file = new System.IO.StreamReader(@"T:\\PARTS\\DATABASE\\PARTS.txt"))
                {
                    while ((line = file.ReadLine()) != null)
                    {
                        if ((backgroundWorker1.CancellationPending == true))
                        {
                            e.Cancel = true;
                        }
                        else if (line.Contains(partNumbersText.Text))
                        {
                            cache[partNumbersText.Text].Add(line);
                            Action action = () => matchesText.Text += (line + Environment.NewLine).ToString();
                            matchesText.Invoke(action); // Or use BeginInvoke
                        }
                    }
            }
    }
    else //this is where you will save time
    {
       foreach(var line in cache[partNumbersText.Text])
       {
           cache[partNumbersText.Text].Add(line);
           Action action = () => matchesText.Text += (line + Environment.NewLine).ToString();
           matchesText.Invoke(action); // Or use BeginInvoke
       }
    }
    

    这只是一个小小的改进

    这不会让你加快很多速度,有几种方法可以让你的程序更快,最重要的一种方法是为你正在搜索的文件建立索引。

    制作索引

    跟踪零件编号在文件中的位置,这不是快速解决方法。您要做的是将具有相关部件号的行的位置保存在单独的文件中。

    【讨论】:

      猜你喜欢
      • 2019-04-30
      • 1970-01-01
      • 2021-10-12
      • 2015-06-04
      • 2014-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多