【问题标题】:Limit size of Queue<T> in .NET?.NET 中 Queue<T> 的限制大小?
【发布时间】:2010-09-05 07:16:37
【问题描述】:

我有一个 Queue 对象,我已将其初始化为容量 2,但显然这只是容量,并且在我添加项目时它会不断扩展。是否已经有一个对象可以在达到限制时自动使项目出列,或者是创建我自己的继承类的最佳解决方案?

【问题讨论】:

    标签: .net collections queue


    【解决方案1】:

    我已经完成了我正在寻找的基本版本,它并不完美,但它会完成这项工作,直到出现更好的东西。

    public class LimitedQueue<T> : Queue<T>
    {
        public int Limit { get; set; }
    
        public LimitedQueue(int limit) : base(limit)
        {
            Limit = limit;
        }
    
        public new void Enqueue(T item)
        {
            while (Count >= Limit)
            {
                Dequeue();
            }
            base.Enqueue(item);
        }
    }
    

    【讨论】:

    • 我通过从限制属性集中调用来稍微扩充代码,以确保队列大小没有超过限制 - 只是一个简单的大于限制的出队。除此之外,这是一个很好且简单的解决方案,谢谢。
    • 很好地了解更改“限制”属性的“设置器”代码。
    • 这个类有一个非常严重的限制,Marcus Griep 在他的回答中暗示了这一点:因为你的 Enqueue 方法被声明为 new (因为 Queue&lt;T&gt;.Enqueue 不是虚拟的),如果有人将您的 LimitedQueue&lt;T&gt; 转换为 Queue&lt;T&gt; 他们将能够添加任意数量的项目,而您的限制不会生效。我还建议将if (this.Count &gt;= this.Limit) 更改为while (this.Count &gt;= this.Limit),只是为了安全起见(例如,对于我刚才提到的场景)。
    • 如果Queue的其他方法调用Enqueue(),会调用原来的Enqueue,会导致严重问题
    【解决方案2】:

    我建议您打开C5 Library。与 SCG(System.Collections.Generic)不同,C5 被编程为接口并被设计为子类。大多数公共方法是虚拟的,并且没有一个类是密封的。这样,您就不必使用如果您的 LimitedQueue&lt;T&gt; 被转换为 SCG.Queue&lt;T&gt; 时不会触发的 icky “new” 关键字。使用 C5 并使用与以前几乎相同的代码,您将派生自 CircularQueue&lt;T&gt;CircularQueue&lt;T&gt; 实际上同时实现了堆栈和队列,因此您几乎可以免费获得这两个选项的限制。我在下面用一些 3.5 结构重写了它:

    using C5;
    
    public class LimitedQueue<T> : CircularQueue<T>
    {
        public int Limit { get; set; }
    
        public LimitedQueue(int limit) : base(limit)
        {
            this.Limit = limit;
        }
    
        public override void Push(T item)
        {
            CheckLimit(false);
            base.Push(item);
        }
    
        public override void Enqueue(T item)
        {
            CheckLimit(true);
            base.Enqueue(item);
        }
    
        protected virtual void CheckLimit(bool enqueue)
        {
            while (this.Count >= this.Limit)
            {
                if (enqueue)
                {
                    this.Dequeue();
                }
                else
                {
                    this.Pop();
                }
            }
        }
    }
    

    我认为这段代码应该完全符合您的要求。

    【讨论】:

      【解决方案3】:

      我希望这门课对你有帮助:
      在内部,循环 FIFO 缓冲区使用具有指定大小的 Queue。 一旦达到缓冲区的大小,它将用新的项目替换旧项目。

      注意:您不能随意删除项目。我将方法 Remove(T item) 设置为返回 false。 如果您愿意,您可以修改以随机删除项目

      public class CircularFIFO<T> : ICollection<T> , IDisposable
      {
          public Queue<T> CircularBuffer;
      
          /// <summary>
          /// The default initial capacity.
          /// </summary>
          private int capacity = 32;
      
          /// <summary>
          /// Gets the actual capacity of the FIFO.
          /// </summary>
          public int Capacity
          {
              get { return capacity; }          
          }
      
          /// <summary>
          ///  Initialize a new instance of FIFO class that is empty and has the default initial capacity.
          /// </summary>
          public CircularFIFO()
          {            
              CircularBuffer = new Queue<T>();
          }
      
          /// <summary>
          /// Initialize a new instance of FIFO class that is empty and has the specified initial capacity.
          /// </summary>
          /// <param name="size"> Initial capacity of the FIFO. </param>
          public CircularFIFO(int size)
          {
              capacity = size;
              CircularBuffer = new Queue<T>(capacity);
          }
      
          /// <summary>
          /// Adds an item to the end of the FIFO.
          /// </summary>
          /// <param name="item"> The item to add to the end of the FIFO. </param>
          public void Add(T item)
          {
              if (this.Count >= this.Capacity)
                  Remove();
      
              CircularBuffer.Enqueue(item);
          }
      
          /// <summary>
          /// Adds array of items to the end of the FIFO.
          /// </summary>
          /// <param name="item"> The array of items to add to the end of the FIFO. </param>
           public void Add(T[] item)
          { 
              int enqueuedSize = 0;
              int remainEnqueueSize = this.Capacity - this.Count;
      
              for (; (enqueuedSize < item.Length && enqueuedSize < remainEnqueueSize); enqueuedSize++)
                  CircularBuffer.Enqueue(item[enqueuedSize]);
      
              if ((item.Length - enqueuedSize) != 0)
              {
                  Remove((item.Length - enqueuedSize));//remaining item size
      
                  for (; enqueuedSize < item.Length; enqueuedSize++)
                      CircularBuffer.Enqueue(item[enqueuedSize]);
              }           
          }
      
          /// <summary>
          /// Removes and Returns an item from the FIFO.
          /// </summary>
          /// <returns> Item removed. </returns>
          public T Remove()
          {
              T removedItem = CircularBuffer.Peek();
              CircularBuffer.Dequeue();
      
              return removedItem;
          }
      
          /// <summary>
          /// Removes and Returns the array of items form the FIFO.
          /// </summary>
          /// <param name="size"> The size of item to be removed from the FIFO. </param>
          /// <returns> Removed array of items </returns>
          public T[] Remove(int size)
          {
              if (size > CircularBuffer.Count)
                  size = CircularBuffer.Count;
      
              T[] removedItems = new T[size];
      
              for (int i = 0; i < size; i++)
              {
                  removedItems[i] = CircularBuffer.Peek();
                  CircularBuffer.Dequeue();
              }
      
              return removedItems;
          }
      
          /// <summary>
          /// Returns the item at the beginning of the FIFO with out removing it.
          /// </summary>
          /// <returns> Item Peeked. </returns>
          public T Peek()
          {
              return CircularBuffer.Peek();
          }
      
          /// <summary>
          /// Returns the array of item at the beginning of the FIFO with out removing it.
          /// </summary>
          /// <param name="size"> The size of the array items. </param>
          /// <returns> Array of peeked items. </returns>
          public T[] Peek(int size)
          {
              T[] arrayItems = new T[CircularBuffer.Count];
              CircularBuffer.CopyTo(arrayItems, 0);
      
              if (size > CircularBuffer.Count)
                  size = CircularBuffer.Count;
      
              T[] peekedItems = new T[size];
      
              Array.Copy(arrayItems, 0, peekedItems, 0, size);
      
              return peekedItems;
          }
      
          /// <summary>
          /// Gets the actual number of items presented in the FIFO.
          /// </summary>
          public int Count
          {
              get
              {
                  return CircularBuffer.Count;
              }
          }
      
          /// <summary>
          /// Removes all the contents of the FIFO.
          /// </summary>
          public void Clear()
          {
              CircularBuffer.Clear();
          }
      
          /// <summary>
          /// Resets and Initialize the instance of FIFO class that is empty and has the default initial capacity.
          /// </summary>
          public void Reset()
          {
              Dispose();
              CircularBuffer = new Queue<T>(capacity);
          }
      
          #region ICollection<T> Members
      
          /// <summary>
          /// Determines whether an element is in the FIFO.
          /// </summary>
          /// <param name="item"> The item to locate in the FIFO. </param>
          /// <returns></returns>
          public bool Contains(T item)
          {
              return CircularBuffer.Contains(item);
          }
      
          /// <summary>
          /// Copies the FIFO elements to an existing one-dimensional array. 
          /// </summary>
          /// <param name="array"> The one-dimensional array that have at list a size of the FIFO </param>
          /// <param name="arrayIndex"></param>
          public void CopyTo(T[] array, int arrayIndex)
          {
              if (array.Length >= CircularBuffer.Count)
                  CircularBuffer.CopyTo(array, 0);           
          }
      
          public bool IsReadOnly
          {
              get { return false; }
          }
      
          public bool Remove(T item)
          {
              return false; 
          }
      
          #endregion
      
          #region IEnumerable<T> Members
      
          public IEnumerator<T> GetEnumerator()
          {
             return CircularBuffer.GetEnumerator();
          }
      
          #endregion
      
          #region IEnumerable Members
      
          IEnumerator IEnumerable.GetEnumerator()
          {
              return CircularBuffer.GetEnumerator();
          }
      
          #endregion
      
          #region IDisposable Members
      
          /// <summary>
          /// Releases all the resource used by the FIFO.
          /// </summary>
          public void Dispose()
          {          
              CircularBuffer.Clear();
              CircularBuffer = null;
              GC.Collect();
          }
      
          #endregion
      }
      

      【讨论】:

      • 我认为通过使用此代码,您可以拥有一个有限大小的队列..这也是循环缓冲区。
      【解决方案4】:

      并发解决方案

      public class LimitedConcurrentQueue<ELEMENT> : ConcurrentQueue<ELEMENT>
      {
          public readonly int Limit;
      
          public LimitedConcurrentQueue(int limit)
          {
              Limit = limit;
          }
      
          public new void Enqueue(ELEMENT element)
          {
              base.Enqueue(element);
              if (Count > Limit)
              {
                  TryDequeue(out ELEMENT discard);
              }
          }
      }
      

      注意:由于Enqueue 控制元素的添加,并且一次添加一个,因此无需为TryDequeue 执行while

      【讨论】:

        【解决方案5】:

        您应该创建自己的类,环形缓冲区可能会满足您的需求。

        .NET 中允许指定容量的数据结构(数组除外)使用它来构建用于保存内部数据的内部数据结构。

        例如,对于列表,容量用于确定内部数组的大小。当您开始向列表中添加元素时,它会从索引 0 开始填充这个数组,当它达到您的容量时,它会将容量增加到一个新的更高容量,并继续填充它。

        【讨论】:

          【解决方案6】:

          为什么不直接使用大小为 2 的数组?队列应该能够动态增长和收缩。

          或者围绕Queue&lt;T&gt; 实例创建一个包装类,每次将&lt;T&gt; 对象入队时,检查队列的大小。如果大于 2,则将第一项出列。

          【讨论】:

            【解决方案7】:

            如果对任何人有用,我发了一个LimitedStack&lt;T&gt;

            public class LimitedStack<T>
            {
                public readonly int Limit;
                private readonly List<T> _stack;
            
                public LimitedStack(int limit = 32)
                {
                    Limit = limit;
                    _stack = new List<T>(limit);
                }
            
                public void Push(T item)
                {
                    if (_stack.Count == Limit) _stack.RemoveAt(0);
                    _stack.Add(item);
                }
            
                public T Peek()
                {
                    return _stack[_stack.Count - 1];
                }
            
                public void Pop()
                {
                    _stack.RemoveAt(_stack.Count - 1);
                }
            
                public int Count
                {
                    get { return _stack.Count; }
                }
            }
            

            当它变得太大时,它会删除最旧的项目(堆栈底部)。

            (这个问题是“C# 限制堆栈大小”的最高 Google 结果)

            【讨论】:

            • 此代码 99% 正确。但是,如果我们调用 Peek 或 Pop 时没有将任何内容放入堆栈,它将崩溃,因为索引为 -1。这可以通过添加索引边界检查来轻松解决。
            • 建议在 Peek 和 Pop() 中添加以下内容: if ((_stack.Count - 1)
            • 郑重声明,如果没有额外的锁定,这段代码就不是线程安全的(这绝对没问题,线程安全从来不是这个类的设计规范的一部分)。
            【解决方案8】:

            您可以使用LinkedList&lt;T&gt; 并添加线程安全:

            public class Buffer<T> : LinkedList<T>
            {
                private int capacity;
            
                public Buffer(int capacity)
                {
                    this.capacity = capacity;   
                }
            
                public void Enqueue(T item)
                {
                    // todo: add synchronization mechanism
                    if (Count == capacity) RemoveLast();
                    AddFirst(item);
                }
            
                public T Dequeue()
                {
                    // todo: add synchronization mechanism
                    var last = Last.Value;
                    RemoveLast();
                    return last;
                }
            }
            

            需要注意的一点是,在此示例中,默认枚举顺序将是 LIFO。但如有必要,可以覆盖它。

            【讨论】:

              猜你喜欢
              • 2010-11-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-01-22
              • 2011-11-21
              • 2018-11-12
              • 2023-03-27
              • 1970-01-01
              相关资源
              最近更新 更多