【问题标题】:Queue<T> vs List<T>队列<T> 与列表<T>
【发布时间】:2012-05-09 23:25:37
【问题描述】:

我目前使用List&lt;T&gt; 作为队列(使用lst[0] 然后lst.removeAt(0))来保存对象。在给定时间最多大约有 20 个项目。我意识到有一个实际的Queue&lt;T&gt; 类。我想知道使用Queue&lt;T&gt; 而不是像队列一样使用List&lt;T&gt; 是否有任何好处(性能、内存等)?

【问题讨论】:

  • Probably 如果您使用的项目不超过 20 个,则不会。但是您可以使用 StopWatch 类来衡量这一点。
  • 这是否重要取决于您的使用场景。 lst.RemoveAt(0) 将导致列表重新定位所有元素,而队列更智能。理论上队列更好,但要确保你应该衡量你的用例。
  • 您不能按索引访问队列。你必须使用你出列的条目,你不能把它们放回去。 Peek 不是解决方案,但 Count > 0 可能是。

标签: .net performance list queue difference


【解决方案1】:

可以分析性能。尽管在这种项目很少的情况下,您可能需要运行代码数百万次才能真正获得有价值的差异。

我会这样说:Queue&lt;T&gt; 会更明确地暴露你的意图,人们知道队列是如何工作的。

像队列一样使用的列表不是很清楚,特别是如果您有很多不必要的索引和RemoveAt(magicNumber) 代码。从代码维护的角度来看,Dequeue 更具消耗性。

如果这会给您带来可衡量的性能问题,您可以解决它。不要预先解决所有潜在性能问题。

【讨论】:

  • 为什么我们不应该提前解决所有潜在的性能问题?
  • @JohnIsaiahCarmona:因为使用 O(n^2) 算法而不是 O(n) 10 个元素不是性能问题。
  • @JohnIsaiahCarmona 因为你在不需要的时候就掉进了微优化的陷阱。我对这一切的看法是,我们应该提防明显的碰撞,但是 A 和 B 之间的几亚毫秒不值得担心,直到它们成为问题。在大多数情况下,可维护、可读的代码比性能更重要。
  • @JohnIsaiahCarmona 此外,大多数潜在的性能问题从未真正意识到,因此它们只是潜在问题。
  • 我们不要直接解雇@JohnIsaiahCarmona。我们应该始终鼓励开发人员了解这些东西是如何在幕后实现的。更实际地说,我们都知道这些故事是如何投入生产的——有些将一些“临时”的东西放在一起,快进 5 年,它仍在发货,现在其他一些组件正在以性能惩罚相互调节的方式对其进行改造。这可能不是很适合这个问题,但我认识的最好的开发人员会努力了解他们的代码是如何受到冲击/压力的。
【解决方案2】:

简短回答:
Queue&lt;T&gt; 在用作队列时比 List&lt;T&gt; 快。当像列表一样使用时,List&lt;T&gt;Queue&lt;T&gt; 快​​。

长答案:
Queue&lt;T&gt; 用于出队操作更快,这是一个 O(1) 操作。数组后续项的整个块不会向上移动。这是可能的,因为Queue&lt;T&gt; 不需要帮助从随机位置移除,而只需要从顶部移除。所以它保持了一个头部(在Dequeue 上拉出项目)和尾部位置(在Enqueue 上添加项目)。另一方面,从List&lt;T&gt; 的顶部移除需要自己将每个后续项目的位置向上移动。这是 O(n) - 如果您从顶部移除,这是最坏的情况,这就是出队操作。如果您在循环中出队,则速度优势可能会很明显。

如果您需要索引访问、随机检索等,List&lt;T&gt; 的性能会更高。Queue&lt;T&gt; 必须完全枚举才能找到合适的索引位置(它不会暴露 IList&lt;T&gt;)。

也就是说,Stack&lt;T&gt;List&lt;T&gt; 更接近,推送和弹出操作没有性能差异。它们都推送到数组结构的末尾并从末尾删除(两者都是 O(1))。


当然,你应该使用正确的结构来揭示意图。在大多数情况下,它们的性能也会更好,因为它们是为此目的量身定制的。我相信如果根本没有性能差异,微软就不会仅仅为了不同的语义而将Queue&lt;T&gt;Stack&lt;T&gt; 包含在框架中。如果是这样的话,它会很容易扩展。想想SortedDictionary&lt;K, V&gt;SortedList&lt;K, V&gt;,两者的功能完全相同,但仅通过性能特征进行区分;他们在 BCL 找到了一席之地。

【讨论】:

  • 在 Add-heavy 情况下怎么办?
  • @AlexanderRyanBaggett 应该没什么区别(我的最佳猜测),但您应该真正使用能够更好地揭示意图的结构。他们都向开发者讲述了不同的故事。
【解决方案3】:

除了Queue&lt;T&gt; 类实现队列和List&lt;T&gt; 类实现列表这一事实之外,还有性能差异。

每次从List&lt;T&gt; 中删除第一个元素时,队列中的所有元素都会被复制。队列中只有 20 个元素,它可能并不明显。但是,当您从Queue&lt;T&gt; 中取出下一个元素时,不会发生这样的复制,并且总是会更快。如果队列很长,则差异可能很大。

【讨论】:

    【解决方案4】:

    我想强调一下 HugoRune 已经指出的内容。 QueueList 快得多,在此用例中,List 的内存访问是 1n。我有一个类似的用例,但我有数百个值,我将使用Queue,因为它要快一个数量级。

    关于在List 之上实现Queue 的注释:关键字是“已实现”。它不会在出队时将每个值都复制到新的内存位置,而是使用循环缓冲区。这可以在“List”之上完成,而不会受到直接使用 List 所暗示的副本的惩罚。

    【讨论】:

    • 它不能使用循环缓冲区,除非容量是固定大小的。
    • @Didaxis 根据上面的“原因”,除非容量固定,否则 List 不能使用 Array .. 但实现确实如此,因此 List.Add 是“摊销” O(1) . tldr;链式数组和数组大小调整(即,当由 List 支持时)循环缓冲区实现是可能的..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 2013-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-05
    • 2013-06-24
    相关资源
    最近更新 更多