Return to the lecture notes index

Lecture 18 (March 2, 2009)

Bounded Buffers

The queue we implemented using a linked list is nice in the fact that it can shrink and grow smoothly. Although it isn't a situation we'll run into much in most high-level Java programs, there are environments where this can actuallly be problematic. For example, in many embedded systems, memory is very limited and general purpose memory allocators aren't available. This makes it difficult to manage the growing and shrinking of a linked list.

In these environments a bounded buffer is often employed. A bounded buffer is basically a fixed-length queue. If the finite amount of space is all full -- new items simply cannot be added.

In general, bounded buffers are implemented in arrays. But, they can be implemented in any fixed-length, indexed data structure. Let's look at how.

Implementing Queues With Arrays

Adding and removing at the end of an array is easy. We saw this when we implemented stacks. But, adding and removing at the head is more complicated. To add, we either have to over-write the first element -- or go through O(n) work to shift every element forward. The same is true of removing, where we either need to waste the space, or O(n) effort to shift everything back to fill it. This does not make an array a particularly natural data structure for managing a queue, where opertions happen on boths sides.

But, if we are willing to fix the size of the queue, we can actually manage the queue in a circular way, so that the tail wraps around and uses the space freed by dequeueing at the head.

To implement the fixed-size queue in an array, we still need to implement the "enqueue" and "dequeue" operations. To do this, we define a "head" and "tail" index. When we insert an item into the queue, we increment the tail index and then insert it at tail index. When we remove an item from the queue, we remove it from the head index, and then increment the head index. If the head or tail index becomes higher than the number of items in the vector, we reset it to 0.

In order to make this work, all we have to do is make sure that the tail index never steps on the head index, because that would mean that we have overwritten one of the values in the queue. Otherwise, we can enqueue and dequeue as we please, and the head and tail indexes will circle through the array and maintain the first-in-first-out behavior.

Implementing A Queue With An Array

The following class implements a queue using a ArrayList.


import java.util.*;


/* This class implements a queue using a ArrayList.  It has the standard queue
 * operations enEqueue and deQueue, as well as a peek method to see the first
 * item without removing it, and an isEmpty method to check if there is
 * anything currently in the queue.
 *
 * Since we are implementing a queue, we have to fix the size of the ArrayList
 * that we are using.  This size is determined as a parameter to the
 * constructor.  The head and tail of the queue are managed by the headIndex
 * and tailIndex instance variables
 */
class Queue
{
   private Object[] queue;   // stores the items in the queue
   private int headIndex;     // the index of the first item to remove
   private int tailIndex;     // the index where we can add the next item
   private int count;         // much easier than trying to interpret indexes


   /*
    * The constructor for the Queue class.  It takes in the
    * number of items that the queue can hold as a parameter
    */
   public ArrayListQueue(int size)
   {
      // initialize the vector and set it to the specified size
      queue = new Object[size];

      // start both the head and tail at the first spot 
      headIndex = 0;
      tailIndex = 0;
      count = 0;
   }


   /*
    * This method adds a new item to the queue at the tail
    */
   public boolean enqueue(Object addObj) {
      /*
       * check that the queue is not full.
       *
       * if the head and tail are the same, then either the queue is
       * empty or the queue is full.  if the queue is full, then the
       * index where the tail is will already have something in it
       */
      if (count == size) {
         // throwing an Exception would probably be better, but we'll
         // keep it simple

         return false;
      }


      /*
       * change the element at the tail index to reference the new item.
       *
       * we have to use setElementAt() and not one of the add() or insert()
       * methods because we don't want to change the size of the vector
       */
      queue[tailIndex] = addObj;

      // increment the tail index, or reset it to 0 if we reach the end
      tailIndex = (tailIndex + 1) % queue.length;

      // Note that the object has been added
      count++;

      return true;
   }


   /*
    * This method removes the item at the head of the queue
    */
   public Object dequeue()
   {
      if (count <= 0) {
        // Again, an Exception is the Right Thing, but 
        // we'll reutnr null for chalkboard expediency

        return null;
      }


      // create a new reference to the item we want to dequeue
      Object dequeueObj = queue[headIndex];

      /*
       * remove the item by setting the head index to null
       *
       */
      queue[headIndex] = null;

      // increment the head index, or reset it to 0 if we reach the end
      headIndex = (headIndex + 1) % queue.size();

      // Note that the object is gone
      count--;

      // return the object that we remove from the queue
      return dequeueObj;
   }


   /*
    * This method returns the object at the head of the queue, but does
    * not remove it
    */
   public Object peek()
   {
      if (count <= 0) {
        // Again, an Exception is the Right Thing, but 
        // we'll reutnr null for chalkboard expediency

        return null;
      }

      // return the object that is currently at the head index
      return queue[headIndex];
   }


   /*
    * This method tests whether or not the queue is currently empty
    *
    * if there is nothing at the head index, then there is nothing in
    * the queue
    */
   public boolean isEmpty() {
     return (count > 0);
   }
}