我正在做一个项目(在。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<>和它附带的各种交叉方法
似乎有几个瓶颈已经被指出了。
如果我没理解错的话,你是:
- 读取两个文件到两个列表。O (K)
- 在一个列表(O(n))上迭代,在另一个列表(O(m))中搜索匹配项。
- 创建包含这些匹配项的新列表。(O (n))
所以你有一个有序的东西:O(K + m * n * n)
。瓶颈发生在步骤2和3(代码中的内部循环)。
解决方案:
- 你正在搜索的集合(slAllObjects我认为)应该是你可以快速搜索的东西,所以要么使用哈希集,要么排序一次,然后使用二进制搜索来查找这个集合中的项目。
- 预分配您正在创建的列表。您提前知道大小,所以设置容量匹配。
这个解决方案应该减少O(n^2) * O(m)
到O(n) * O(k)
如果你使用哈希集或O(n) * log(m)
如果你排序列表。
您可以做的一个改进是使用AddRange
而不是Add
。AddRange
将允许内部列表预先分配添加所需的内存,而不是在整个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
,因为它应该更快(不使用迭代器,因此,至少对于数组列表,应该更快)。