Intro to the circular buffer or circular array

  • Circular buffer/array:

    • Circular buffer = a data structure that uses an array as if it were connected "end-to-end"

    Schematically:

  • This data structure is also known as:

    • Circular array of Ring buffer

The circular buffer data structure

  • The circular buffer data structure consists of an array with 2 "pointers" or indices:

    • Read pointer = the index of the read position in array
    • Write pointer = the index of the write position in array

    Schematically:

Wrap-around nature of the circular buffer

  • When the write (or read) pointer reaches the end of the array/buffer...

    write(99):

Wrap-around nature of the circular buffer

  • The write (or read) pointer will wrap around and reset to 0:

    write(99):

Implementing the wrap-around nature of the circular buffer

  • The achieve the wrap araound effect, the read (or write) pointer (=index) variable is updated using "modulo arithmetic":

    Normal increment operation:
    
          write = write + 1;
    
         (read  = read  + 1;   // for read operations)
    
    
    Increment operation with modulo arithmetic: write = (write + 1) % buf.length;

  • Example:

       Suppose: write = 15 
    
       write = (write + 1) % buf.length;   // buf.length = 16
    
             = (15 + 1) % 16
    	 = 16 % 16
    	 = 0
    

The write operation on a circular buffer

  • The write( ) operation will store the data at the write pointer and advance it.

    Example: write(99)

    write(99) will store 99 in buf[write] and increment the write pointer

The basic implementation of the write operation on a circular buffer

  • The basic implementation of the write( ) operation (without checking if buffer is full):

        void write(T e) // T is the type of the data stored in the queue
        {
           buf[write] = e;
           write = (write+1)%buf.length; // Use modulo arithmetic !
        }
    

The read operation on a circular buffer

  • The read( ) operation will return the data at the read pointer and advance it.

    Example:

    read( ) will update the read pointer to read=3 and return the value 9

The basic implementation of the read operation on a circular buffer

  • The basic implementation of the read( ) operation (without checking if buffer is empty ):

        T read( ) // T is the type of the data stored in the queue
        {
           T retVal = buf[read];
           read = (read+1)%buf.length;  // Use  modulo arithmetic ! 
           return retVal;
        }
    

How to tell if a circular buffer if empty

  • Consider the following circular buffer with 2 items:

  • The circular buffer is not empty and:

          read != write 
    

How to tell if a circular buffer if empty

  • After reading 2 elements from the circular buffer:

  • The circular buffer is empty and:

          read == write 
    

Now.... how to tell if a circular buffer if full

  • Consider the following circular buffer with 2 empty spots:

  • The circular buffer is not full and:

          read != write 
    

Now.... how to tell if a circular buffer if full

  • After writing 1 element (13) to the circular buffer:

  • The circular buffer is not full yet and:

          read != write 
    

How to tell if a circular buffer if empty

  • After writing another element from the circular buffer:

  • The circular buffer is full and:

          read == write     // Problem: this also mean "empty" 
    

Breaking the ambiguity

  • The following trick is used to break the ambiguity:

    • A circular buffer is full when the is 1 (= one) empty slot left

    Schematically:

I can now show you the implementation of an Integer queue using a circular buffer

Recall:   the Queue interface definition

  • Sample Queue interface definition:

       interface MyQueueInterface<E>
       {
          boolean isEmpty(); // returns true is queue is empty
          boolean isFull();  // returns true is queue is full
    
          void enqueue(E e); // Insert elem e at the back of the queue
    
          E dequeue();       // Remove the elem at the front
                             // of the queue and return it
    
          E peek();          // Return the elem at the front
                             // without removing it
       }

A class that defines the Queue interface must override all these methods....

Variable definitions and the constructor of the IntegerQueue class

  • We implement the IntegerQueue using a circular buffer:

    public class IntegerQueue implements MyQueueInterface<Integer>
    {
        private Integer[] buf;    // Array of the circular buffer
        private int read;         // read pointer (= head index of queue)
        private int write;        // write pointer (= tail index of queue)
    
        // Constructor 
        public IntegerQueue(int N)
        {
            // Variable of the circular buffer
            buf = new Integer[N];   // Create array
            read = 0;               // Initialize read pointer
            write = 0;		// Initialize write pointer
        }
    
        // Interface methods will be discussed next...
        boolean isEmpty() { ... }
        boolean isFull()  { ... }
    
        void enqueue(Integer e) { ... }
        Integer dequeue() {... }
        Integer peek() { ... }
    }
    

The implementation of the isEmpty( ) and isFull( ) methods

  • The queue (or circular buffer) is empty when read pointer == write pointer:

        public boolean isEmpty()  // returns true is queue is empty
        {
            return read == write;
        } 

  • The queue (or circular buffer) is full when there is one (= 1) open spot left:

        public boolean isEmpty()  // returns true is queue is full
        {
            /* -------------------------------------------------
               buffer has 1 open spot
               <==> write 1 item into the buffer and it's full
    	   ------------------------------------------------- */
            return (write+1)%buf.length == read;
        } 

The implementation of the isEmpty( ) and isFull( ) methods

  • The enqueue( ) method:

        public void enqueue(Integer e) 
        {
            if ( isFull () )
            {
                System.out.println("Full"); // Or throw exception
                return ;
            }
    
            buf[ write ] = e;
            write = (write+1)%buf.length; // Use modulo arithmetic
        }

  • The dequeue( ) method:

        public Integer dequeue() 
        {
            if ( isEmpty() )
            {
                System.out.println("Empty"); // Or throw exception
                return null;
            }
    
            Integer retVal = buf[ read ];
            read = (read+1)%buf.length; // Use modulo arithmetic
            return retVal;
        } 

The implementation of the peek( ) method

  • The peek( ) method:

        public Integer peek() 
        {
            if ( isEmpty() )
            {
                System.out.println("Empty"); // Or throw exception
                return null;
            }
    	else
    	{  
                return buf[ read ];
            }
        } 

DEMO: 10-queue/01-circular-buf/Demo.java + IntegerQueue.java