【问题标题】:How do I implement a circular list (ring buffer) in C?如何在 C 中实现循环列表(环形缓冲区)?
【发布时间】:2010-09-17 22:49:09
【问题描述】:

如何实现一个循环列表,当它已满时覆盖最旧的条目?

作为背景知识,我想在 GWT 中使用循环列表;所以使用 3rd 方库 不是我想要的。

【问题讨论】:

    标签: c queue


    【解决方案1】:

    这是一个用于循环缓冲区 (FIFO) 的简单模板解决方案。它确实使一个存储空间空着,但我认为这对性能和简单性来说是一个小小的损失。 我包括了一个简单的压力测试。

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class E: public std::exception {
    
        const char *_msg;
        E(){}; //no default constructor
    
    public:
    
        explicit E(const char *msg) throw(): _msg(msg) {};
        const char * what() const throw() {return(_msg);};
    
    };
    
    const int min_size = 2;
    const int max_size = 1000;
    
    template<typename T>
    class Fifo{
    
        int _head;
        int _tail;
        int _size;
    
        T* _storage;
    
    public:
    
        explicit Fifo(int size = min_size);
        ~Fifo(){ delete [] _storage;};
    
        bool is_full() const{
            return(((_head+1)%_size) == _tail);
        };
        bool is_empty() const{
            return(_head == _tail);
        };
    
        void add_item(const T& item);
        const T& get_item();
    
    };
    
    template<typename T>
    Fifo<T>::Fifo(int size): _size(size){
        
        if (size < min_size) throw E("Cannot create Fifo less than 2\n");
    
        _head = _tail = 0;
    
        try{
    
            _storage = new T[_size];
        }
        catch (std::bad_alloc &ba)
        {
            char e_string[500];
            sprintf(e_string, "Cannot allocate memory (%s)\n", ba.what());
            throw E(e_string);
        }
    
        printf("Constructing Fifo of size %d\n", _size);
    
    }
    
    template <typename T>
    void Fifo<T>::add_item(const T& item)
    {
        if (this->is_full()) throw E("Fifo is full.\n");
    
        _storage[_head] = item;
    
        _head = (_head + 1)%_size;
    }
    
    template <typename T>
    const T& Fifo<T>::get_item()
    {
        if (this->is_empty()) throw E("Fifo is empty.\n");
    
        int temp = _tail; //save the current tail
    
        _tail = (_tail+1)%_size; //update tail
    
        return(_storage[temp]);
    }
    
    int main()
    {
        Fifo<int> my_fifo(3);
    
        for (int i = 1, item; i < 50; i++)
        {
            my_fifo.add_item(i);
            my_fifo.add_item(i*10);
            item = my_fifo.get_item();
            printf("Item: %d\n", item);
            item = my_fifo.get_item();
            printf("Item: %d\n", item);
        }
    
    
        return 0;
    }
    

    【讨论】:

      【解决方案2】:

      这是一种使用 java 创建动态增加/减少循环队列的优雅方法。

      我已经注释了大部分代码以便于快速理解。希望对你有帮助:)

          public class CircularQueueDemo {
          public static void main(String[] args) throws Exception {
      
              CircularQueue queue = new CircularQueue(2);
              /* dynamically increasing/decreasing circular queue */
              System.out.println("--dynamic circular queue--");
              queue.enQueue(1);
              queue.display();
              queue.enQueue(2);
              queue.display();
              queue.enQueue(3);
              queue.display();
              queue.enQueue(4);
              queue.display();
              queue.deQueue();
              queue.deQueue();
              queue.enQueue(5);
              queue.deQueue();    
              queue.display();
      
          }
      }
      
      class CircularQueue {
          private int[] queue;
          public int front;
          public int rear;
          private int capacity;
      
          public CircularQueue(int cap) {
              front = -1;
              rear = -1;
              capacity = cap;
              queue = new int[capacity];
          }
      
          public boolean isEmpty() {
              return (rear == -1);
          }
      
          public boolean isFull() {
              if ((front == 0 && rear == capacity - 1) || (front == rear + 1))
                  return true;
              else
                  return false;
          }
      
          public void enQueue(int data) { 
              if (isFull()) {            //if queue is full then expand it dynamically   
                  reSize();                    
                  enQueue(data);
              } else {                                 //else add the data to the queue
                  if (rear == -1)                      //if queue is empty
                      rear = front = 0;
                  else if (rear == capacity)          //else if rear reached the end of array then place rear to start (circular array)
                      rear = 0;
                  else
                      rear++;                         //else just incement the rear 
                  queue[rear] = data;                 //add the data to rear position
              }
          }
      
          public void reSize() {
              int new_capacity = 2 * capacity;                  //create new array of double the prev size
              int[] new_array = new int[new_capacity];          
      
              int prev_size = getSize();                        //get prev no of elements present
              int i = 0;                                        //place index to starting of new array
      
              while (prev_size >= 0) {                          //while elements are present in prev queue
                  if (i == 0) {                                 //if i==0 place the first element to the array
                      new_array[i] = queue[front++];
                  } else if (front == capacity) {               //else if front reached the end of array then place rear to start (circular array) 
                      front = 0;
                      new_array[i] = queue[front++];
                  } else                                        //else just increment the array
                      new_array[i] = queue[front++];
                  prev_size--;                                  //keep decreasing no of element as you add the elements to the new array
                  i++;                                          //increase the index of new array
              }
              front = 0;                                        //assign front to 0
              rear = i-1;                                       //assign rear to the last index of added element
              capacity=new_capacity;                            //assign the new capacity
              queue=new_array;                                  //now queue will point to new array (bigger circular array)
          }
      
          public int getSize() {
              return (capacity - front + rear) % capacity;                  //formula to get no of elements present in circular queue
          }
      
          public int deQueue() throws Exception {
              if (isEmpty())                                       //if queue is empty
                  throw new Exception("Queue is empty");
              else {
                  int item = queue[front];                        //get item from front
                  if (front == rear)                              //if only one element
                      front = rear = -1;
                  else if (front == capacity)                     //front reached the end of array then place rear to start (circular array)
                      front = 0;
                  else
                      front++;                                    //increment front by one
                  decreaseSize();                                 //check if size of the queue can be reduced to half
                  return item;                                    //return item from front
              }
      
          }
      
          public void decreaseSize(){                           //function to decrement size of circular array dynamically
              int prev_size = getSize();
              if(prev_size<capacity/2){                         //if size is less than half of the capacity
                  int[] new_array=new int[capacity/2];          //create new array of half of its size
                  int index=front;                              //get front index
                  int i=0;                                      //place an index to starting of new array (half the size)
                  while(prev_size>=0){                          //while no of elements are present in the queue
                      if(i==0)                                  //if index==0 place the first element
                          new_array[i]=queue[front++];
                      else if(front==capacity){                 //front reached the end of array then place rear to start (circular array)      
                          front=0;
                          new_array[i]=queue[front++];
                      }
                      else
                          new_array[i]=queue[front++];         //else just add the element present in index of front
                      prev_size--;                             //decrease the no of elements after putting to new array 
                      i++;                                     //increase the index of i
                  }
                  front=0;                                     //assign front to 0
                  rear=i-1;                                    //assign rear to index of last element present in new array(queue)
                  capacity=capacity/2;                         //assign new capacity (half the size of prev)
                  queue=new_array;                             //now queue will point to new array (or new queue)
              }
          }
      
          public void display() {                           //function to display queue
              int size = getSize();
              int index = front;
      
              while (size >= 0) {
                  if (isEmpty())
                      System.out.println("Empty queue");
                  else if (index == capacity)
                      index = 0;
                  System.out.print(queue[index++] + "=>");
                  size--;
              }
              System.out.println("  Capacity: "+capacity);
      
          }
      
      }
      

      输出:

      --动态循环队列--

      1=> 容量:2

      1=>2=>容量:2

      1=>2=>3=>容量:4

      1=>2=>3=>4=>容量:4

      4=>5=>容量:2

      【讨论】:

        【解决方案3】:

        我不认为队列是制作缓存的最佳方式。你想成为你的缓存真的很快!除非您希望缓存非常小或者您的内存非常有限,否则对队列进行线性扫描不是可行的方法。

        假设您不想要非常小的缓存或慢速缓存,使用带有值的哈希映射到链表中节点的链表是一个不错的方法。您总是可以逐出头部,并且每当访问一个元素时,您可以将其移除并将其放在列表的头部。对于访问,您可以直接获取它或检查它是否在 O(1) 中的缓存中。驱逐一个元素也是 O(1),更新列表也是如此。

        举个例子,看看java中的LinkedHashMap。

        http://docs.oracle.com/javase/6/docs/api/java/util/LinkedHashMap.html

        【讨论】:

        • 如果您将其用作真正的 FIFO 并且不需要随机访问,我相信在几乎所有情况下,循环缓冲区都会比链表快。无需分配/释放内存,驱逐前面的元素就像增加/减少索引一样简单。
        • @BenHershey 你是对的。我不认为这是我回答的问题。从我的答案来看,我似乎在回答缓存的实现细节,而不是真正的 FIFO 队列。也许问题变了,或者我在回答时看错了。
        • 好吧有道理。我只是想为未来阅读可能会感到困惑的人澄清。 ;)
        【解决方案4】:

        我将它用于我的微控制器。 为简化代码,一个字节将不填。 Aka size - 1 实际上是全部容量。

        fifo_t* createFifoToHeap(size_t size)
        {
            byte_t* buffer = (byte_t*)malloc(size);
        
            if (buffer == NULL)
                return NULL;
        
            fifo_t* fifo = (fifo_t*)malloc(sizeof(fifo_t));
        
            if (fifo == NULL)
            {
               free(buffer);
               return NULL;
            }
        
            fifo->buffer = buffer;
            fifo->head = 0;
            fifo->tail = 0;
            fifo->size = size;
        
            return fifo;
        }
        
        #define CHECK_FIFO_NULL(fifo) MAC_FUNC(if (fifo == NULL) return 0;)
        
        size_t fifoPushByte(fifo_t* fifo, byte_t byte)
        {
            CHECK_FIFO_NULL(fifo);
        
            if (fifoIsFull(fifo) == true)
               return 0;
        
            fifo->buffer[fifo->head] = byte;
        
            fifo->head++;
            if (fifo->head == fifo->size)
               fifo->head = 0;
        
            return 1;
        }
        
        size_t fifoPushBytes(fifo_t* fifo, byte_t* bytes, size_t count)
        {
            CHECK_FIFO_NULL(fifo);
        
            for (uint32_t i = 0; i < count; i++)
            {
                if (fifoPushByte(fifo, bytes[i]) == 0)
                    return i;
            }
        
            return count;
        }
        
        size_t fifoPopByte(fifo_t* fifo, byte_t* byte)
        {
            CHECK_FIFO_NULL(fifo);
        
            if (fifoIsEmpty(fifo) == true)
                return 0;
        
            *byte = fifo->buffer[fifo->tail];
        
            fifo->tail++;
            if (fifo->tail == fifo->size)
                fifo->tail = 0;
        
            return 1;
        }
        
        size_t fifoPopBytes(fifo_t* fifo, byte_t* bytes, size_t count)
        {
            CHECK_FIFO_NULL(fifo);
        
            for (uint32_t i = 0; i < count; i++)
            {
                if (fifoPopByte(fifo, bytes + i) == 0)
                    return i;
            }
        
            return count;
        }
        
        bool fifoIsFull(fifo_t* fifo)
        {
            if ((fifo->head == (fifo->size - 1) && fifo->tail == 0) || (fifo->head == (fifo->tail - 1)))
                return true;
            else
                return false;
        }
        
        bool fifoIsEmpty(fifo_t* fifo)
        {
            if (fifo->head == fifo->tail)
                return true;
            else
                return false;
        }
        
        size_t fifoBytesFilled(fifo_t* fifo)
        {
            if (fifo->head == fifo->tail)
                return 0;
            else if ((fifo->head == (fifo->size - 1) && fifo->tail == 0) || (fifo->head == (fifo->tail - 1)))
                return fifo->size;
            else if (fifo->head < fifo->tail)
                return (fifo->head) + (fifo->size - fifo->tail);
            else
                return fifo->head - fifo->tail; 
        }
        

        【讨论】:

        • 您好,您忘记在代码中添加 struct fifo_t... :)
        • 相信你可以从createFifoToHeap()函数中派生出来。其中fifo-&gt;buffer = buffer; fifo-&gt;head = 0; fifo-&gt;tail = 0; fifo-&gt;size = size;缓冲区为byte_t,头尾为uint32_t,大小为size_t
        【解决方案5】:

        一个非常简单的实现,用 C 表示。实现一个循环缓冲区样式的 FIFO 队列。可以通过创建一个包含队列大小、队列数据和队列索引(输入和输出)的结构来使其更通用,这些结构将与数据一起传入以添加或从队列中删除。然后这些相同的例程可以处理多个队列。另请注意,这允许任何大小的队列,但如果您使用 2 的幂并进一步自定义代码,则可以使用加速。

        /* Very simple queue
         * These are FIFO queues which discard the new data when full.
         *
         * Queue is empty when in == out.
         * If in != out, then 
         *  - items are placed into in before incrementing in
         *  - items are removed from out before incrementing out
         * Queue is full when in == (out-1 + QUEUE_SIZE) % QUEUE_SIZE;
         *
         * The queue will hold QUEUE_ELEMENTS number of items before the
         * calls to QueuePut fail.
         */
        
        /* Queue structure */
        #define QUEUE_ELEMENTS 100
        #define QUEUE_SIZE (QUEUE_ELEMENTS + 1)
        int Queue[QUEUE_SIZE];
        int QueueIn, QueueOut;
        
        void QueueInit(void)
        {
            QueueIn = QueueOut = 0;
        }
        
        int QueuePut(int new)
        {
            if(QueueIn == (( QueueOut - 1 + QUEUE_SIZE) % QUEUE_SIZE))
            {
                return -1; /* Queue Full*/
            }
        
            Queue[QueueIn] = new;
        
            QueueIn = (QueueIn + 1) % QUEUE_SIZE;
        
            return 0; // No errors
        }
        
        int QueueGet(int *old)
        {
            if(QueueIn == QueueOut)
            {
                return -1; /* Queue Empty - nothing to get*/
            }
        
            *old = Queue[QueueOut];
        
            QueueOut = (QueueOut + 1) % QUEUE_SIZE;
        
            return 0; // No errors
        }
        

        【讨论】:

        • 如果我错了,请纠正我,但这不允许您仅存储 99 个条目吗?表达式 (in == (out-1+SIZE)%SIZE) 表示 in 在 out 之前是否为 1。但是 in 还没有被写入,所以第 100 个点永远不会被写入。
        • @Jonathon - 这是正确的,虽然这对专家来说很明显,但这是针对初学者的,所以我修改了代码以使其更加明确。感谢您的来信!
        • @RocketRoy 代码是正确的。它用缓冲区中的一个空间来换取更简单的空/满测试。
        • @AdamDavis 代码不直观与不正确不同。此实现具有规定的QUEUE_ELEMENTS 数量,并在所有边缘情况下生成正确的结果,并且比您的实现具有更少的簿记变量。简单有效。
        • @RocketRoy 我不同意你的观点。虽然还有其他方法可以实现具有 FIFO 功能的循环列表,但此代码并非不正确,并且确实有效。它确实留下了一个向后爬过缓冲区的洞,但这是设计使然,并且是实现 FIFO 的一种非常省时/处理效率的方法。我还简要回顾了您建议更好的代码,并且在这一点上也不同意,但似乎其他人在那里留下了 cmets,这使得我提出了许多观点。我在这个答案中提供的实现并不完美,但它简单、易懂且有效。
        【解决方案6】:

        如果你想要一个固定长度的循环列表。您可以使用(动态)数组。使用两个变量进行管理。一个用于下一个元素的位置,一个用于计算元素个数。

        Put:将元素放在空闲位置。移动位置(模长度)。将 1 添加到计数,除非计数等于列表的长度。 获取:仅当计数>0 时。将位置向左移动(模长度)。减少计数。

        【讨论】:

          【解决方案7】:

          使用数组并在第一个可用位置保留一个变量 P。

          每次添加新元素时增加 P。

          要知道数组中 P 的等效索引,请执行 (P % n) 其中 n 是数组的大小。

          【讨论】:

            【解决方案8】:

            使用链表。为头部和尾部保持单独的指针。从列表的头部弹出,推到尾部。如果你想要它是圆形的,只要确保新的尾巴总是指向头部。

            我可以理解为什么您可能希望使用链表来实现 FIFO,但为什么要使用循环列表呢?

            【讨论】:

              猜你喜欢
              • 2010-10-24
              • 1970-01-01
              • 1970-01-01
              • 2012-11-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多