我正在进行测试。
看起来像:
方法1)
List<int> = new List<int>{1,2,4, .....} //assume 1000k
var result ErrorCodes.Where(x => ReturnedErrorCodes.Contains(x)).First();
方法2)
List<int> = new List<int>{1,2,4, .....} //assume 1000k
var result = ErrorCodes.Where(x => ReturnedErrorCodes.Contains(x)).ToArray()[0];
为什么方法2与方法1相比是如此之慢?
答案 0 :(得分:60)
你有一个装有一千个硬币的罐子,其中许多是硬币。你想要一毛钱。以下是解决问题的两种方法:
一次一个地从罐子里掏出硬币,直到你拿到一角硬币。现在你有一毛钱。
将硬币从罐子里拉出来,一次一个,把硬币放在另一个罐子里。如果那个罐子太小,可以将它们一次一个地移动到一个更大的罐子里。继续这样做,直到你在最后一个罐子里有所有硬币。那个罐子可能太大了。制造一个足够大的罐子来容纳那么多硬币,然后将硬币一次一个地移动到新的罐子里。现在开始从那个罐子里拿出硬币。取出第一个。现在你有一毛钱。
现在清楚为什么方法1比方法2快很多?
答案 1 :(得分:46)
嗯...因为你正在创建一个额外的数组(而不仅仅是使用迭代器)。第一种方法在第一次匹配后停止(Where
是非缓冲的流API)。第二个将 所有 匹配加载到一个数组中(可能有多个重新调整大小),然后获取第一个项目。
作为旁注;你可以创造无限的序列;第一种方法仍然可行,第二种方法将永远运行(或爆炸)。
也可能是:
var result ErrorCodes.First(x => ReturnedErrorCodes.Contains(x));
(这不会让它变得更快,但也许更容易阅读)
答案 2 :(得分:4)
由于延期执行。
代码ErrorCodes.Where(x => ReturnedErrorCodes.Contains(x))
不返回整数集合,而是返回一个能够返回整数流的表达式。在你开始从中读取整数之前,它不会做任何实际的工作。
ToArray
方法将使用整个流并将所有整数放入数组中。这意味着必须将整个列表中的每个项目与错误代码进行比较。
另一方面,First
方法只会从流中获取第一个项目,然后停止从流中读取。这将使它更快,因为它会在找到匹配后立即停止将列表中的项目与错误代码进行比较。
答案 3 :(得分:1)
因为ToArray()
将整个序列复制到数组中。
方法2必须迭代整个序列以构建数组,然后返回第一个元素。
方法1只是遍历足够的序列以找到第一个匹配元素。
答案 4 :(得分:1)
ToArray()
遍历它所给出的整个序列并创建并排出它。
如果您不是ToArray()
,First()
允许Where()
只返回匹配并立即返回的第一个项目。
答案 5 :(得分:1)
First()是O(1)
的复杂性ToArray()[0]是复杂度O(n)+1
答案 6 :(得分:1)
var @e = array.GetEnumerator();
// First
@e.MoveNext();
return @e.Current;
// ToArray (with yield [0] should as fast as First...)
while (@e.MoveNext() {
yield return @e.Current;
}
答案 7 :(得分:0)
因为在第二个示例中,您实际上是将IEnumerable<T>
转换为数组,而在第一个示例中,没有进行任何转换。
答案 8 :(得分:0)
在方法2中,必须首先将整个数组转换为数组。此外,当First()
更具可读性时,混合数组访问似乎很尴尬。
答案 9 :(得分:0)
这是有道理的,ToArray可能涉及一个副本,这总是会更昂贵,因为Linq不能保证你将如何使用你的数组,而First()可以只返回单个元素在开头。