我的问题是基于this question提出的,我在这个问题上发布了答案。here
这是代码。
var lines = System.IO.File.ReadLines(@"C:\test.txt");
var Minimum = lines[0];//Default length set
var Maximum = "";
foreach (string line in lines)
{
if (Maximum.Length < line.Length)
{
Maximum = line;
}
if (Minimum.Length > line.Length)
{
Minimum = line;
}
}
使用LINQ(我的方法)替代此代码
var lines = System.IO.File.ReadLines(@"C:\test.txt");
var Maximum = lines.OrderByDescending(a => a.Length).First().ToString();
var Minimum = lines.OrderBy(a => a.Length).First().ToString();
LINQ易于阅读和实现..
我想知道哪一个对性能有益。 Linq如何在内部为OrderByDescending和OrderBy进行长度排序?
答案 0 :(得分:16)
你可以read the source code for OrderBy。
停止对代码执行micro-optimizing或premature-optimization。尝试编写正确执行的代码,然后如果您以后遇到性能问题,请分析您的应用程序并查看问题所在。如果你有一段代码由于找到最短和最长的字符串而出现性能问题,那么就开始优化这部分。
我们应该忘记效率低,大约97%的时间说: 过早优化是万恶之源。但我们不应该通过 我们在关键的3%中获得机会 - 唐纳德克努特
File.ReadLines
正在返回IEnumerable<string>
,这意味着如果您对其进行预告,它将逐一向您返回数据。我认为你可以在这里做的最好的性能改进是改进从磁盘读取文件。如果它足够小,可以将整个文件加载到内存中使用File.ReadAllLines
,如果不是尝试读取适合内存的大块文件。逐行读取文件会因磁盘的I / O操作而导致性能下降。所以问题不在于LINQ或循环如何执行,问题在于磁盘读取次数。
答案 1 :(得分:8)
在我看来,你需要了解决定最佳方法的一些要点。
首先,让我们想要用LINQ解决问题。然后,要编写最优化的代码,您必须了解延迟执行。大多数Linq方法,例如Select
,Where
,OrderBy
,Skip
,Take
以及其他一些方法都使用DE。那么,什么是延期执行?这意味着,除非用户不需要,否则不会执行这些方法。这些方法只会创建迭代器。当我们需要它时,这个迭代器就可以执行了。那么,用户如何让它们执行?答案是,在foreach
的帮助下,它将调用GetEnumerator
或其他Linq方法。例如,ToList()
,First()
,FirstOrDefault()
,Max()
和其他一些。
这些过程将帮助我们获得一些表现。
现在,让我们回到你的问题。 File.ReadLines
将返回IEnumerable<string>
,这意味着,除非我们需要,否则它不会读取行。在您的示例中,您有两次调用此对象的排序方法,这意味着它将再次对此集合进行两次排序。而不是那样,你可以对集合进行一次排序,然后调用ToList()
来执行OrderedEnumerable
迭代器,然后获取集合中的第一个和最后一个元素,这些元素实际上在我们手中。
var orderedList = lines
.OrderBy(a => a.Length) // This method uses deferred execution, so it is not executed yet
.ToList(); // But, `ToList()` makes it to execute.
var Maximum = orderedList.Last();
var Minimum = orderedList.First();
顺便说一句,您可以找到OrderBy
源代码here。
返回OrderedEnumerable
实例,排序算法在此处:
public IEnumerator<TElement> GetEnumerator()
{
Buffer<TElement> buffer = new Buffer<TElement>(source);
if (buffer.count > 0)
{
EnumerableSorter<TElement> sorter = GetEnumerableSorter(null);
int[] map = sorter.Sort(buffer.items, buffer.count);
sorter = null;
for (int i = 0; i < buffer.count; i++) yield return buffer.items[map[i]];
}
}
现在,让我们回到影响表现的另一个方面。如果你看到,Linq使用另一个元素来存储已排序的集合。当然,它需要一些记忆,这告诉我们它不是最有效的方式。
我只是想解释一下Linq是如何工作的。但是,我非常同意@Dotctor对你的整体回答。只是,不要忘记,你可以使用不会返回File.ReadAllLines
的{{1}},而是IEnumerable<stirng>
。
这是什么意思?正如我在开始时尝试解释的那样,区别在于,如果它是string[]
,那么当enuemrator枚举迭代器时,.net将逐行读取行。但是,如果它是IEnumerable
,那么我们的应用程序内存中的所有行。
答案 2 :(得分:8)
使用第二种方法,您不仅要对行进行两次排序......您读取文件两次。这是因为File.ReadLines
会返回IEnumerable<string>
。这清楚地说明了为什么你不应该枚举IEnumerable<>
两次,除非你知道它是如何构建的。如果您确实想这样做,请添加.ToList()
或.ToArray()
,以便将IEnumerable<>
表示为集合...而第一种方法的内存占用为一行文本(因为它一次读取一行文件),第二种方法将整个文件加载到内存中进行排序,因此将有更大的内存占用,如果文件是几百mb,差异是big(请注意,从技术上讲,你可以拥有一行长1gb的单行文件,所以这条规则不是绝对的......对于合理的文件来说,行长达数百个字符:-))
现在......有人会告诉你,过早的优化是邪恶,但我会告诉你,无知是两次邪恶。
如果您知道两个代码块之间的区别,那么您可以在两个代码之间做出明智的选择...否则您只是随意扔石头直到它看起来有效。 似乎工作的位置是此处的关键字。
答案 3 :(得分:7)
最有效的方法是在这里避免使用LINQ,使用foreach
的方法只需要一次枚举。
如果你想把整个文件放到一个集合中,你可以使用它:
List<string> orderedLines = System.IO.File.ReadLines(@"C:\test.txt")
.OrderBy(l => l.Length)
.ToList();
string shortest = orderedLines.First();
string longest = orderedLines.Last();
除此之外,您应该阅读LINQ's deferred execution。
另请注意,您的LINQ方法不仅会对所有行进行两次排序以获得最长和最短的时间,而且还需要读取整个文件两次,因为File.ReadLines
使用StreamReader
(相反)到ReadAllLines
首先将所有行读入数组中。
MSDN:
当您使用
ReadLines
时,您可以开始枚举该集合 返回整个集合之前的字符串;当你使用ReadAllLines
,您必须等待返回整个字符串数组 在你可以访问数组之前
通常,这有助于提高LINQ查询的效率,例如:如果您使用Where
过滤掉行,但在这种情况下,它会让事情变得更糟。
正如Jeppe Stig Nielsen在评论中提到的那样,由于OrderBy
需要在内部创建另一个缓冲区集合(第二个是ToList
),所以还有另一种方法可能更有效:
string[] allLines = System.IO.File.ReadAllLines(@"C:\test.txt");
Array.Sort(allLines, (x, y) => x.Length.CompareTo(y.Length));
string shortest = allLines.First();
string longest = allLines.Last();
Array.Sort
的唯一缺点是它执行的是不稳定的排序,而不是OrderBy
。因此,如果两条线的长度相同,则可能无法保持订单。