优化c#中的列表性能



我正在做一个项目(在。net 3.5中),它读取2个文件,然后比较它们并找到丢失的对象。

基于此数据,我需要进一步解析它并定位对象位置。我将尝试进一步解释:

我有两个列表:1 list是一个非常长的服务器上的所有文件列表,以及它们在服务器或其他服务器上的物理地址,这个文件有10亿多行长,并且还在不断增长(我知道这有点荒谬)。文件大小目前在160MB左右。另一个列表是报告列表,显示服务器上丢失的文件。与列表1相比,这个列表非常小,通常小于1MB。

我必须将列表2与列表1相交并确定丢失的对象的位置。列表中的项目看起来像这样(不幸的是,它是空格分隔的,不是CSV文档):文件名。扩展rev rev#源服务器:harddriveLocation|filenameOnServer。扩展起源

使用流,我将两个文件读入单独的字符串列表。然后,我取一个正则表达式并将列表2中的项解析为包含文件名的第三个列表。扩展,rev和rev#。这一切都很精彩,是它的表演让我受不了。 我希望有一种更有效的方法来做我正在做的事情。
foreach (String item in slMissingObjectReport)
{
    if (item.Contains(".ext1") || item.Contains(".ext2") || item.Contains(".ext3"))
    {
        if (!item.Contains("|"))
        {                                     
            slMissingObjects.Add(item + "," + slMissingObjectReport[i + 1] + "," + slMissingObjectReport[i + 2]); //object, rev, version
        }
    }
    i++;
}
int j = 1; //debug only
foreach (String item in slMissingObjects)
{
    IEnumerable<String> found = Enumerable.Empty<String>();
    Stopwatch matchTime = new Stopwatch(); //used for debugging
    matchTime.Start(); //start the stop watch
    foreach (String items in slAllObjects.Where(s => s.Contains(item.Remove(item.IndexOf(',')))))
    {
        slFoundInAllObjects.Add(item);
    }
matchTime.Stop();
tsStatus.Text = "Missing Object Count: " + slMissingObjects.Count + " | " + "All Objects count: " + slAllObjects.Count + " | Time elapsed: " + (taskTime.ElapsedMilliseconds) * 0.001 + "s | Items left: " + (slMissingObjects.Count - j).ToString();
j++;
}
taskTime.Stop();
lstStatus.Items.Add(("Time to complete all tasks: " + (taskTime.ElapsedMilliseconds) * 0.001) + "s");

这是有效的,但由于目前在我的丢失对象列表中有1300个丢失的项目,平均需要8到12分钟才能完成。耗时最长的部分是

foreach (String items in slAllObjects.Where(s => s.Contains(item.Remove(item.IndexOf(',')))))
{
    slFoundInAllObjects.Add(item);
}

我只需要一个正确方向的点,以及如何改进我正在工作的代码。LINQ并不像看起来的那样是杀手,它只是把它添加到一个似乎会杀死性能的列表中。

哈希集是专门为这种任务设计的,当你有唯一的值,你需要比较它们。

列表则不是。它们只是任意的集合。

我的第一个目标是使用HashSet<>和它附带的各种交叉方法

似乎有几个瓶颈已经被指出了。

如果我没理解错的话,你是:

  1. 读取两个文件到两个列表。O (K)
  2. 在一个列表(O(n))上迭代,在另一个列表(O(m))中搜索匹配项。
  3. 创建包含这些匹配项的新列表。(O (n))

所以你有一个有序的东西:O(K + m * n * n)。瓶颈发生在步骤2和3(代码中的内部循环)。

解决方案:

  1. 你正在搜索的集合(slAllObjects我认为)应该是你可以快速搜索的东西,所以要么使用哈希集,要么排序一次,然后使用二进制搜索来查找这个集合中的项目。
  2. 预分配您正在创建的列表。您提前知道大小,所以设置容量匹配。

这个解决方案应该减少O(n^2) * O(m)O(n) * O(k)如果你使用哈希集或O(n) * log(m)如果你排序列表。

您可以做的一个改进是使用AddRange而不是AddAddRange将允许内部列表预先分配添加所需的内存,而不是在整个foreach循环过程中多次分配。

IEnumerable<string> items = slAllObjects.Where(s => s.Contains(item.Remove(item.IndexOf(','));
slFoundInAllObjects.AddRange(items);

其次,您可能应该避免在Where lambda中使用item.Remove(item.IndexOf(','),因为这将导致它对列表中的每个项执行一次。该值是静态的,您可以提前设置一次。

var itemWithoutComma = item.Remove(item.IndexOf(','));
IEnumerable<string> items = slAllObjects.Where(s => s.Contains(itemWithoutComma));
slFoundInAllObjects.AddRange(items);

首先,不要使用List。使用hashset可以更快地插入和比较。

接下来,确定列表是否按照预先排序的顺序,如果是,那么您可以同时快速读取两个文件,并且只对每个文件进行一次遍历,根本不必将它们保存在内存中。

如果这些方法都失败了,可以考虑使用LINQ的Intersects方法,它可能会比你自己开发的要好得多。

除了已经提出的建议之外,我还会考虑使用树木。如果我理解正确的话,文件名中有某种层次结构(即:服务器,文件路径,文件名等),对吗?通过使用树,可以大大减少每一步的搜索空间。

此外,如果在每个节点中使用Dictionary<String, Node>,则可以减少搜索时间,考虑到等量的层次结构级别,搜索时间变为O(1)

同样,如果你决定使用数组或数组列表,避免使用foreach而使用for,因为它应该更快(不使用迭代器,因此,至少对于数组列表,应该更快)。

相关内容

  • 没有找到相关文章

最新更新