linq函数OrderByDescending和OrderBy如何在字符串长度内部工作?它比使用循环更快吗?

san*_*mar 6 .net c# linq performance for-loop

我的问题是基于募这个问题,我已经对这个问题发布了一个答案.. 在这里

这是代码.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用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();
Run Code Online (Sandbox Code Playgroud)

LINQ易于阅读和实现..

我想知道哪一个对性能有好处.以及Linq如何在内部为OrderByDescending和OrderBy进行长度排序

Ham*_*jam 16

您可以阅读OrderBy的源代码.

停止对代码进行微优化过早优化.尝试编写正确执行的代码,然后如果您以后遇到性能问题,请分析您的应用程序并查看问题所在.如果由于找到最短和最长的字符串而有一段代码存在性能问题,那么就开始优化这部分.

我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源.然而,我们不应该放弃那个关键的3%的机会 - 唐纳德克努特

File.ReadLines返回一个IEnumerable<string>,这意味着如果你对它做了一个foreach,它将逐个返回给你的数据.我认为你可以在这里做的最好的性能改进是改进从磁盘读取文件.如果它足够小,可以将整个文件加载到内存使用中File.ReadAllLines,如果不是尝试读取适合内存的大块文件.逐行读取文件会因磁盘的I/O操作而导致性能下降.所以这里的问题不是LINQ或循环如何执行,问题在于磁盘读取次数.

  • `ReadLine`在内部使用[ReadBuffer](http://referencesource.microsoft.com/#mscorlib/system/io/streamreader.cs,ef2abdf7bd65b2ec),可以从磁盘读取数据块.@TimSchmelter (3认同)
  • `ReadLines`可能在内部使用"大块".它不一定对枚举器上的每个`.MoveNext()`或`.Current`调用进行I/O操作. (2认同)

Far*_*yev 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();
Run Code Online (Sandbox Code Playgroud)

顺便说一下,你可以在这里找到OrderBy源代码.

它返回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]];
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们回到影响性能的另一个方面.如果你看到,Linq使用另一个元素来存储已排序的集合.当然,它需要一些记忆,这告诉我们这不是最有效的方式.

我只是想解释一下Linq是如何工作的.但是,我非常同意@Dotctor对你的整体回答.只是,不要忘记,你可以使用File.ReadAllLines哪些不会返回IEnumerable<stirng>,但是string[].这是什么意思?正如我在开始时尝试解释的那样,区别在于,如果是IEnumerable,那么当enuemrator枚举迭代器时,.net将逐行读取.但是,如果是string[],那么我们的应用程序内存中的所有行.

  • 而且,从长远来看,`O(n log n)`并不比'O(2 n log n)`更好. (4认同)
  • 2O(n log n)= O(2 n log n)= O(n log n)@Yura (4认同)
  • @DavidArno是的,排序两次所需的时间是排序一次的两倍,但是当你根本不需要排序时,进行*that*优化是没有意义的.没有"O(2 n log n)"这样的东西,所以当可以切换到"O(n)"时,没有必要从"O(n log n)"切换到"O(n log n)". (2认同)

xan*_*tos 8

使用第二种方法,您不仅要对行进行两次排序......您正在读取文件两次.这是因为File.ReadLines返回一个IEnumerable<string>.这清楚地说明了为什么你不应该枚举IEnumerable<>两次,除非你知道它是如何构建的.如果你真的想这样做,添加一个.ToList()或一个.ToArray()将实现它IEnumerable<>的集合...而第一个方法的内存占用单行文本(因为它一次读取一行文件),第二种方法是将整个文件加载到内存中进行排序,因此会有更大的内存占用量,如果文件大约是100 mb,差别很大(请注意,从技术上讲,你可以使用一行文件文本长1gb,所以这个规则不是绝对的......对于合理的文件,其行长达数百个字符:-))

现在......有人会告诉你,过早的优化是邪恶的,但我会告诉你,无知是邪恶的两倍.

如果您知道两个代码块之间的区别,那么您可以在两个代码之间做出明智的选择...否则您只是随机抛出岩石,直到它看起来有效.凡似乎工作是这里的关键词.


Tim*_*ter 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();
Run Code Online (Sandbox Code Playgroud)

除此之外,你应该阅读LINQ的延期执行.

另请注意,您的LINQ方法不仅命令所有行两次获得最长和最短,它还需要读取整个文件两次,因为File.ReadLines使用a 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();
Run Code Online (Sandbox Code Playgroud)

唯一的缺点Array.Sort是它执行不稳定的排序而不是OrderBy.因此,如果两条线具有相同的长度,则可能无法维持该顺序.

  • 我认为`OrderBy`必须慢一些.也许它会将所有内容复制到新的集合中,然后在那里进行就地排序?更有可能的是,它逐渐将源生成的新项目插入到某种类型的新(私有)排序列表中.但速度慢并不意味着"缓慢".所以它在实践中可能是无关紧要的.您对"排序"不稳定的评论是相关的!我们不知道长度相等的线的顺序是否重要. (3认同)