【数据结构】队列与实现分析

 

概念

 

(百度百科)队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

 

从概念上看,跟栈多少有几分相似之处,同样是特殊的线性代表,同样只能在两端操作,唯一的区别除了名字意外,就是线性结构头和尾操作的那点区别了。栈是一种先进后出的结构,那么队列就是一种先进先出的机构。队列在现实中还算是比较常见的,例如排队就是一种队列结构的“实现”,谁排第一位(先进),谁就先获得优先权(先出),当然,插队是这种不文明现象就不值得一提了。在栈的学习场景中我知道栈分别有循序栈和链表栈,主要的区别在于栈使用的实现结构数组和链表。同理,队列同样可以基于这两种基础结构分别实现静态队列和链表队列,此外还有一个叫循环队列的类别,说白了,跟循环链表的概念差不多,通过首尾相连达到一个循环状的结构。

 

在学习队列的特性过程中,结合现实的一些场景就可以很好理解了。接下莱看看队列结构的一些主要操作有哪些:

InitQueue()   ——初始化队列

EnQueue()      ——进队列

DeQueue()      ——出队列

IsQueueEmpty()  ——判断队列是否为空

IsQueueFull()    ——判断队列是否已满

以上为队列结构的一些主要操作,再来结合Java语言中ArrayBlockingQueue类对这些操作的实现吧:

public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable{
    ……
}
public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E> {
    ……
}
public interface BlockingQueue<E> extends Queue<E> {
    ……
}
public interface Queue<E> extends Collection<E> {
    ……
}

从以上ArrayBlockingQueue类的名称和集成关系来看,ArrayBlockingQueue是一个基于数组实现的阻塞队列。再来看看各队列方法的实现:

1 public ArrayBlockingQueue(int capacity, boolean fair) {
2   if (capacity <= 0)
3         throw new IllegalArgumentException();
4   this.items = (E[]) new Object[capacity];
5   lock = new ReentrantLock(fair);
6   notEmpty = lock.newCondition();
7   notFull =  lock.newCondition();
8 }

从ArrayBlockingQueue构造方法可以看出,它定义了一个Object数组和相关锁的实例,这些锁是确保ArrayBlockingQueue的线性安全的。

 1 public boolean offer(E e) {
 2   if (e == null) throw new NullPointerException();
 3   final ReentrantLock lock = this.lock;
 4   lock.lock();
 5   try {
 6   if (count == items.length)
 7        return false;
 8      else {
 9            insert(e);
10            return true;
11        }
12     } finally {
13         lock.unlock();
14     }
15 }

以上为ArrayBlockingQueue的插入队列尾部元素的方法,它是通过Lock锁的机制实现线性安全的。

 1 public E poll() {
 2     final ReentrantLock lock = this.lock;
 3     lock.lock();
 4     try {
 5     if (count == 0)
 6            return null;
 7         E x = extract();
 8         return x;
 9     } finally {
10         lock.unlock();
11     }
12 }

以上为ArrayBlockingQueue获取队列头部元素的方法,同样是通过Lock机制实现线性安全。接下来看看offer中的insert(e)和poll()中extract()方法:

 1 private void insert(E x) {
 2   items[putIndex] = x;
 3   putIndex = inc(putIndex);
 4   ++count;
 5   notEmpty.signal();
 6 }
 7 private E extract() {
 8   final E[] items = this.items;
 9   E x = items[takeIndex];
10   items[takeIndex] = null;
11   takeIndex = inc(takeIndex);
12   --count;
13   notFull.signal();
14   return x;
15 }

从上面两个队列操作方法可以看到,插入是依赖于putIndex数组标签,而取出是依赖于takeIndex数组标签,每插入或取出元素都会对各自标签进行inc()自增,再来看看inc()方法:

1 final int inc(int i) {
2     return (++i == items.length)? 0 : i;
3 }

显而易见,这是一个循环数组队列,当数组索引达到数组长度最大值,又重新回到数组0位的开始重新循环存放元素,前提队列没有被填满。

posted @ 2016-04-06 14:46  wc的一些事一些情  阅读(417)  评论(0编辑  收藏  举报