【问题标题】:PeekRange on a stack in C#?C# 中的堆栈上的 PeekRange?
【发布时间】:2026-01-30 09:05:03
【问题描述】:

我有一个程序需要存储数据值并定期获取最后的“x”数据值。

它最初认为堆栈是要走的路,但我需要能够看到的不仅仅是顶部值 - 类似于 PeekRange 方法,我可以在其中查看最后“x”个值。

目前我只是使用一个列表并获取最后一个,比如 20 个这样的值:

var last20 = myList.Skip(myList.Count - 20).ToList();

该列表在程序运行时一直在增长,但我只需要最后 20 个值。有人可以就更好的数据结构提供一些建议吗?

【问题讨论】:

    标签: c# .net data-structures stack


    【解决方案1】:

    你说堆栈,但你也说你只想要最后 20 个项目。我认为这两个要求并不能真正结合在一起。

    我会说 Johannes 对环形缓冲区的看法是正确的。在 .NET 中自己实现这一点非常容易;只需使用Queue<T>,一旦达到容量 (20) 就开始在每个入队(推送)上出队(弹出)。

    如果您希望您的 PeekRange 从最近到最近进行枚举,您可以定义GetEnumerator 来执行类似return _queue.Reverse().GetEnumerator(); 的操作

    【讨论】:

      【解决方案2】:

      既然您提到了堆栈,我想您只需要在列表末尾进行修改?

      在这种情况下,列表实际上是一个不错的解决方案(缓存效率高,最后插入/删除速度快)。但是,您提取最后几项的方式效率较低,因为IEnumerable<T> 不会公开列表提供的随机访问。所以Skip()-Implementation 必须扫描整个 List 直到它到达末尾(或者先进行运行时类型检查以检测容器实现了IList<T>)。通过索引直接访问项目或(如果您需要第二个数组)使用List<T>.CopyTo() 会更有效。

      如果您需要在开始时快速移除/插入,您可能需要考虑使用环形缓冲区或(双重)链表(请参阅LinkedList<T>)。链表的缓存效率较低,但从两个方向导航和更改都很容易且高效。环形缓冲区实现起来有点困难,但缓存和空间效率更高。因此,如果只存储小值类型或引用类型,它可能会更好。尤其是当缓冲区大小固定时。

      【讨论】:

        【解决方案3】:

        您可以在每次添加后删除at(0)(如果列表长于 20),因此列表永远不会超过 20 项。

        【讨论】:

          【解决方案4】:

          糟糕,.Take() 不会这样做。

          这是 .TakeLast() 的一个实现

          http://www.codeproject.com/Articles/119666/LINQ-Introducing-The-Take-Last-Operators.aspx

          【讨论】:

            【解决方案5】:

            我可能会使用ring buffer。自己实现一个并不难,AFAIK 框架没有提供实现..

            【讨论】: