队列概念
什么是队列?队列就是一个队伍,队列和栈一样,由一段连续的存储空间组成,是一个具有自身特殊规则的数据结构,我们都知道栈的先进后出的规则,而队列刚好相反,是一个先进先出的(FIFO)或者说后进后出(LILO)的数据结构。
队列的是一种受限制的数据结构,插入操作只能从一端操作,这一端叫做队尾,而移除操作也只能从另一端操作,这一端叫队头。我们把插入和移除操作分别叫做入队和出队。
一般而言,队列的实现有两种方式,数组和链表,用数组实现队列有两种方式,一种是顺序队列,一种是循环队列。在顺序存储方式中,队列会有两个标记。
队列的相关操作
出队:
一开始两个标记都指向数组下标0的位置,分别为head(队头指针)和tail(队尾指针)。当一个元素入队后,tail指针会响应地加1;
入队:
当有一个元素进行出队操作,即这个元素从队头移除了,此时head指针会相应地往后移动一位,此时新队列的队头就是原来队列的第二个元素。
思考一个问题:当一个顺序队列进入若干个元素后,队列已经满了,不能再进行入队操作了,我们称之为“真上溢”,此时我对其中的一部分元素进行出队操作,那么队列此时已经不是满的状态了,但是我们仍然不能进行入队操作,这种状态我们称之为“假上溢”。那么如何解决假上溢的问题呢?这时就要用到存储的第二种方式,循环队列了。
当顺序队列出现假上溢的时候,其实队列前端还有空间,我们可以不用把标记指向数组外的地方,只需要把标记重新指向开始的地方就可以了。就是把这个数组首尾连接,成为一个圈,那么此时我们又能进行入队操作。这样就解决了“假上溢”的问题
其实,如果细心的同学还会发现一个问题,如果使用循环队列的方式,当队列为空以及队列满的时候,head和tail指针都是出于重叠的状态,这就会出现歧义了,这会让我们分辨不清当head和tail重叠的时候,队列是空的还是对满状态。一般情况,我们为了区分,一般在循环队列中规定队列的长度为所存储数组的长度减去1,即有一个位置不放元素。这样的话,当head==tail时,说明队列为空,当head == (tail +1)%length时,队列为满的状态
以上是队列的相关概念下面以代码的形式实现队列的相关操作:·
public class ArrayQueue {
private Object[] items;
private int head = 0;
private int tail = 0;
/**
* init the queue
* @param capacity: the length of queue
*/
public ArrayQueue(int capacity) {
this.items = new Object[capacity];
}
/**
* put item into queue
* @param item
* @return
*/
public boolean put(Object item) {
if(head == (tail+1) % items.length) {
return false;
}
items[tail] = item;
tail = (tail+1) % items.length;
return true;
}
/**
* get the head element
* @param head
* @return
*/
public Object peek() {
if(head == tail ) {
return null;
}
return items[head];
}
/**
* poll a element
* @return
*/
public Object poll() {
if(head == tail) {
return null;
}
Object item = items[head];
items[head] =null;
head = (head+1) % items.length;
return item;
}
/**
* isFull
* @return
*/
public boolean isFull() {
return head == (tail +1) % items.length;
}
/**
* isEmpty
* @return
*/
public boolean isEmpty() {
return head == tail;
}
/**
* get queue size
* @return
*/
public int size() {
if(tail >= head) {
return tail - head;
}else {
return tail + items.length -head;
}
}
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(4);
System.out.println(queue.put("A"));//true
System.out.println(queue.put("B"));//true
System.out.println(queue.put("C"));//true
System.out.println(queue.put("D"));//false
System.out.println(queue.isFull());//true
System.err.println(queue.size());//3
System.out.println(queue.peek());//A
System.out.println(queue.poll());//A
System.out.println(queue.poll());//B
System.out.println(queue.poll());//C
System.out.println(queue.isEmpty());//true
}
}
队列的使用场景
在一般程序中,会将队列作为缓冲器或者解耦使用
例如手机在线秒杀用到的队列,前几年某米的饥饿营销,用户想要买一款中意的手机,得现在它的官网上预约,等到开抢的时间,疯狂地点击抢购的按钮,一般来说,每次提供的名额都是供不应求,加入名额只有2000,但是抢购的人有20000,服务器不可能直接处理两百万人的请求,那么真正的处理方法就是:在接受到每个请求后,把这些请求按顺序放在队列的队尾中,然后提示你“正在排队中”,而在队列的另一端,也就是队头,会有一些服务器在处理,根据先后顺序告知用户的抢购结果
还有一个重要的应用就是队列与生产者消费者设计模式的应用,在队列的队尾对接的是生产者,在队头对接的就是消费者。生产者把生产号的产品进行入队操作,在队头消费者去消费出队的产品。因此,对于生产者和消费者来说,有一点是非常重要的,那就是生产的速度和消费的水平要持平,如果生产得太快,而消费得太慢,那么队列就会很长,对于计算机来说,这样占用的空间就会很大。