Circular Buffer

Another common implementation of a queue is a circular buffer. "Buffer" is a general name for a temporary storage location, although it often refers to an array, as it does in this case. What it means to say a buffer is "circular" should become clear in a minute.

The implementation of a circular buffer is similar to the array implementation of a stack, as in Section 19.7. The queue items are stored in an array, and we use indices to keep track of where we are in the array. In the stack implementation, there was a single index that pointed to the next available space. In the queue implementation, there are two indices:first points to the space in the array that contains the first customer in line and next points to the next available space.

The following figure shows a queue with two items (represented by dots).

There are two ways to think of the variables first and last. Literally, they are integers, and their values are shown in boxes on the right. Abstractly, though, they are indices of the array, and so they are often drawn as arrows pointing to locations in the array. The arrow representation is convenient, but you should remember that the indices are not references; they are just integers.

Here is an incomplete array implementation of a queue:

class Queue { 
    public: 

      pvector<Node> array; 

      int first, next; 

      Queue () { 
          array.resize (128); 
          first = 0; 
          next = 0; 
      } 

      bool empty () { 
        return first == next; 
      } 

The instance variables and the constructor are straightforward, although again we have the problem that we have to choose an arbitrary size for the array. Later we will solve that problem, as we did with the stack, by resizing the array if it gets full.

The implementation of empty is a little surprising. You might have thought that first == 0 would indicate an empty queue, but that neglects the fact that the head of the queue is not necessarily at the beginning of the array. Instead, we know that the queue is empty if head equals next, in which case there are no items left. Once we see the implementation of insert and remove, that situation will more more sense.

    void insert (Node node) { 
        array[next] = node; 
        next++; 
    } 

    Node remove () { 
        first++; 
        return array[first-1]; 
    } 

insert looks very much like push in Section 19.7; it puts the new item in the next available space and then increments the index.

remove is similar. It takes the first item from the queue and then increments first so it refers to the new head of the queue. The following figure shows what the queue looks like after both items have been removed.

It is always true that next points to an available space. If first catches up with next and points to the same space, thenfirst is referring to an "empty" location, and the queue is empty. I put "empty" in quotation marks because it is possible that the location that first points to actually contains a value (we do nothing to ensure that empty locations contain null); on the other hand, since we know the queue is empty, we will never read this location, so we can think of it, abstractly, as empty.

As an exercise, fix remove so that it returns null if the queue is empty.

The next problem with this implementation is that eventually it will run out of space. When we add an item we incrementnext and when we remove an item we increment first, but we never decrement either. What happens when we get to the end of the array?

The following figure shows the queue after we add four more items:

The array is now full. There is no "next available space," so there is nowhere for next to point. One possibility is that we could resize the array, as we did with the stack implementation. But in that case the array would keep getting bigger regardless of how many items were actually in queue. A better solution is to wrap around to the beginning of the array and reuse the spaces there. This "wrap around" is the reason this implementation is called a circular buffer.

One way to wrap the index around is to add a special case whenever we increment an index:

        next++; 
        if (next == array.length()) next = 0; 

A fancy alternative is to use the modulus operator:

        next = (next + 1) % array.length(); 

Either way, we have one last problem to solve. How do we know if the queue is really full, meaning that we cannot insert another item? The following figure shows what the queue looks like when it is "full."

There is still one empty space in the array, but the queue is full because if we insert another item, then we have to increment next such that next == first, and in that case it would appear that the queue was empty!

To avoid that, we sacrifice one space in the array. So how can we tell if the queue is full?

        if ((next + 1) % array.length == first) 

And what should we do if the array is full? In that case resizing the array is probably the only option.

As an exercise, put together all the code from this section and write an implementation of a queue using a circular buffer that resizes itself when necessary.

时间: 2024-09-17 04:18:45

Circular Buffer的相关文章

VB.NET实现DirectSound9 (7) 录音

关键字: VB.NET DirectX 9 DirectSound 录音 riff文件格式 作者:董含君 下午看了微软提供的例子,居然把录音定位成Beginner级别晕哦,虽说我认为这个例子是微软提供的最"直接"的例子,但是步骤超多.而且还牵扯到多线程开辟缓冲区回调riff文件格式 IO 输出等等.由于录音的复杂性,以及微软这个例子的直接性,坚持原创的我最终还是复制了大量的代码.(希望不要骂我....) OK,先来说录音的步骤,里面牵扯到riff或者使用技巧的地方,有注释.我仅仅说步骤

USB WDM驱动开发实例 bulkusb

参考书籍<<Windows驱动开发技术详解>> 1.该示例介绍如何进行USB驱动的开发. 它全面地支持了即插即用的处理, 也很全面地支持了电源管理,同时很好地支持了USB设备的bulk读写. 如果从头开发 USB 驱动,往往很难达到USB驱动的稳定性,所以建议在此驱动修改基础上进行USB驱动开发.     2.功能驱动与物理总线驱动 程序员不需要了解USB如何将请求化成数据包等细节,只需要指定何种管道,发送何种数据即可. 当功能驱动想向某个管道发出读写请求时,首先构造请求发给USB

PostgreSQL 10.0 preview 功能增强 - 备库支持&quot;时间、字节&quot;六维延迟

标签 PostgreSQL , 10.0 , 时间度量备库延迟 , pg_stat_replication 背景 pg_stat_replication视图中有4个字段记录了从备库反馈的WAL位点.如下: postgres=# \d pg_stat_replication View "pg_catalog.pg_stat_replication" Column | Type | Modifiers ------------------+-------------------------

Java 性能调优指南之 Java 集合概览

[编者按]本文作者为拥有十年金融软件开发经验的 Mikhail Vorontsov,文章主要概览了所有标准 Java 集合类型.文章系国内 ITOM 管理平台 OneAPM 编译呈现,以下为正文: 本文将概览所有标准的 Java 集合类型.我们将按照它们可区分的属性与主要用例进行分类.除此之外,我们还将穷举在不同集合类型之间进行数据转换的方法. 数组(Arrays) 数组是 Java 语言内置的唯一集合类型,尤其擅长处理预先知道数量上限的元素集.java.util.Arrays 包含了许多用于处

directsound抓取麦克风PCM数据封装类

网上有很多方法从麦克风读取PCM数据,不想一一举例.只是在这里发布一个我自己写的directsound的麦克风PCM数据采集类,通过它,可以很方便的利用directsound技术把麦克风的数据采集到,而且本身,开发者不必太在意自己会不会directsound编程,可以很方便的让开发者的主要精力集中于程序本身,而不是细节. 这个是头文件: #pragma once#ifndef _CAPTURE_SOUND_H_#define _CAPTURE_SOUND_H_#include <mmsystem

PostgreSQL 9.6 快照过旧 - 源码浅析

PostgreSQL 9.6 快照过旧 - 源码浅析 作者 digoal 日期 2016-10-05 标签 PostgreSQL , 9.6 , 快照过旧 , snapshot too old 背景 在PostgreSQL 9.6以前,垃圾回收存在的问题. 当vacuum回收垃圾时,遇到垃圾记录的xmax大于数据库中现存的最早未提交事务xmin时,不会对其进行回收. 因此当数据库中存在很久为结束的事务时,可能会导致数据库膨胀. PostgreSQL 9.6加入了快照过旧的功能,目的是防止过长的事

ASH(Active Session History)活动会话历史说明

原文转自:http://blog.csdn.net/tianlesoftware/article/details/6448765 在Oracle 10g中,推出了一些与DB 性能相关的功能,比如AWR, ADDM, STA, 以及这篇讲的ASH(Active Session History).   关于其他的几个功能说明,参考我之前的Blog:        Oracle AWR 介绍        http://blog.csdn.net/tianlesoftware/archive/2009

嵌入式 环形缓冲区的设计与实现

环形缓冲区是嵌入式系统中十分重要的一种数据结构,比如在一个视频处理的机制中,环形缓冲区就可以理解为数据码流的通道,每一个通道都对应着一个环形缓冲区,这样数据在读取和写入的时候都可以在这个缓冲区里循环进行,程序员可以根据自己需要的数据大小来决定自己使用的缓冲区大小.          环形缓冲区,顾名思义这个缓冲区是环形的,那么何谓环形这个意思也很好理解,就是用一个指针去访问该缓冲区的最后一个内存位置的的后一位置时回到环形缓冲区的起点.类似一个环一样.这样形容就很好理解了,当然有办法实现了.我在这

(DirectX系列02)DirectSound 录音编码实现

      前面已经发布了一篇音频特效播放相关的文章,今天开始学习DirectSound录音编码实现,这是DirectSound中另一个重要的地方.学会之后受益无穷啊!不过DirectSound设备录音编码其实不难,只要是调用Windows API按照一定的顺序和方法都不难.        利用DirectSound录音,有三个非常重要的对象,如下:        IDirectSoundCapture:设备对象,根据用户录音的设备创建的设备对象,利用该对象可以获取录音设备的属性.