Sunday, November 28, 2010

Efficient Circular Buffer in Java

Overwriting Circular buffers are great data structures to use if you would like to operate on a recent window of data. Elements are added and removed FIFO like a Queue, except additions on full buffers will cause the oldest (head of the queue) element to be removed. A concrete example of using such a structure is maintaining a frame rate count for recently generated frames in a multimedia application. For example, you could specify a buffer size of 100, and then after each frame generation insert a timestamp into the queue. Once the queue is full, subsequent additions will cause the oldest frame's timestamp to be removed. At any time you can calculate the average frame rate for up to the last 100 frames as (# elements in buffer) / ((newest timestamp) - ( oldest timestamp))

Here is the source code, feel free to use it and do whatever you'd like with it.

 import java.util.NoSuchElementException;  
/**
* Thread safe fixed size circular buffer implementation. Backed by an array.
*
* @author brad
*/
public class ArrayCircularBuffer<T> {
// internal data storage
private T[] data;
// indices for inserting and removing from queue
private int front = 0;
private int insertLocation = 0;
// number of elements in queue
private int size = 0;
/**
* Creates a circular buffer with the specified size.
*
* @param bufferSize
* - the maximum size of the buffer
*/
public ArrayCircularBuffer(int bufferSize) {
data = (T[]) new Object[bufferSize];
}
/**
* Inserts an item at the end of the queue. If the queue is full, the oldest
* value will be removed and head of the queue will become the second oldest
* value.
*
* @param item
* - the item to be inserted
*/
public synchronized void insert(T item) {
data[insertLocation] = item;
insertLocation = (insertLocation + 1) % data.length;
/**
* If the queue is full, this means we just overwrote the front of the
* queue. So increment the front location.
*/
if (size == data.length) {
front = (front + 1) % data.length;
} else {
size++;
}
}
/**
* Returns the number of elements in the buffer
*
* @return int - the number of elements inside this buffer
*/
public synchronized int size() {
return size;
}
/**
* Returns the head element of the queue.
*
* @return T
*/
public synchronized T removeFront() {
if (size == 0) {
throw new NoSuchElementException();
}
T retValue = data[front];
front = (front + 1) % data.length;
size--;
return retValue;
}
/**
* Returns the head of the queue but does not remove it.
*
* @return
*/
public synchronized T peekFront() {
if (size == 0) {
return null;
} else {
return data[front];
}
}
/**
* Returns the last element of the queue but does not remove it.
*
* @return T - the most recently added value
*/
public synchronized T peekLast() {
if (size == 0) {
return null;
} else {
int lastElement = insertLocation - 1;
if (lastElement < 0) {
lastElement = data.length - 1;
}
return data[lastElement];
}
}
}

1 comment:

Michelle Beach said...

Thank you for this. I haven't implemented it yet, but this looks like it will help me with my FIFO 2nd Chance page replacement project. This is the best/most clear example I've found.