队列 特性
操作特性:
队列也是一种操作受限的线性表,只允许在一端插入和删除数据。
跟栈一样,队列也可以使用数据来实现,还可以使用链表来实现。
使用数组实现的队列叫做顺序队列,使用链表实现的队列叫做链式队列。
顺序队列
/**
* use array implement queue
*
* @author liujun
* @version 0.0.1
* @date 2019/08/23
*/ public class MyArrayQueue {
private int [] array ;
/** max queue size */
private final int MaxArraySize ;
private int size ;
private int head ;
private int tail ;
public MyArrayQueueOne ( int maxArraySize) {
this . MaxArraySize = maxArraySize ;
this . array = new int [maxArraySize] ;
this . head = 0 ;
this . tail = 0 ;
}
public void enqueue ( int value) {
if ( tail < MaxArraySize ) {
array [ tail ] = value ;
tail ++ ;
size ++ ;
} else {
throw new IndexOutOfBoundsException( "queue full" ) ;
}
}
public int size () {
return size ;
}
public boolean isfull () {
return size == MaxArraySize ;
}
public int dequeue () {
int getValue = - 1 ;
if ( head < tail ) {
getValue = array [ head ] ;
for ( int i = 1 ; i < tail ; i++) {
array [i - 1 ] = array [i] ;
}
tail = tail - 1 ;
size -- ;
}
return getValue ;
}
}
这是一个基本的队列实现,但存在着问题,那就是数组空间连续性的问题,在每次操作的,会进行数据的所有数据的一次搬移操作。
每次有一个数据从队列中取出,就会触发一次数据的搬移,浪费严重。针对此问题,可采用集中式的触发。即当队列中的数据被填满了,才触发一次数据搬移操作。在队列还没有被填满之前,不进行数据的搬移操作。
public void enqueue ( int value) {
if ( tail <= MaxArraySize ) {
// 如果空间已经被占用,则触发一次搬移操作
if ( tail == MaxArraySize ) {
for ( int i = head ; i < tail ; i++) {
array [i - head ] = array [i] ;
}
tail = head ;
head = 0 ;
}
//
if ( tail < MaxArraySize ) {
array [ tail ] = value ;
tail ++ ;
size ++ ;
}
} else {
throw new IndexOutOfBoundsException( "queue full" ) ;
}
}
循环队列
在顺序队列的实现中,当tail == MaxArraySize 会有数据的搬移操作。必然的会影响到性能,那有没有不搬移数据的实现呢?那这个来看看循环队列的实现思路。
循环队列,类比循环链表, 那就是一个首尾相连的数组。
就像一个环一样,队列的添加与删除总不断的这环中往复。
但要想写出没有bug的环境形队列,关键在于确定队空与队满的判定条件。假如队列大小为n,那么,队空的判定条件就是head==tail,那队满的判断条件呢。先来看看图中所画队满时的环形队列
tail =1、head=2 队列大小为8,可(1+1)%8=2
总结规律就是(tail+1)%n == head,这就是环形队列满的判定条件。
再来看看代码实现吧:
/**
* use array implement cycle queue
*
* @author liujun
* @version 0.0.1
* @date 2019/08/25
*/ public class MyCycleQueue {
private final int [] array ;
private final int capacity ;
private int head ;
private int tail ;
public MyCycleQueue ( int maxSize) {
this . capacity = maxSize ;
this . array = new int [ capacity ] ;
}
public void enqueue ( int value) {
// check cycle is full
if (( tail + 1 ) % capacity == head ) {
throw new IndexOutOfBoundsException( "queue is full" ) ;
} else {
array [ tail ] = value ;
tail = ( tail + 1 ) % capacity ;
}
}
public int dequeue () {
// check cycle is null
int getvalue = - 1 ;
if ( head == tail ) {
throw new NegativeArraySizeException( "queue is empty" ) ;
} else {
getvalue = array [ head ] ;
head = ( head + 1 ) % capacity ;
}
return getvalue ;
}
}
链式队列
链式队列的实现.
因为链表只涉及节点的删除与添加操作。没有数据搬移的操作,实现起来也比较简单。但因为链表是内存不连续,对于CPU的缓存不友好。
/**
* use linked implement queue
*
* @author liujun
* @version 0.0.1
* @date 2019/08/23
*/ public class MyLinkedQueue {
class LinkNode {
private int val ;
private LinkNode next ;
public LinkNode ( int valu) {
this . val = valu ;
}
}
/** max queue size */
private final int MaxQueueSize ;
private LinkNode root = new LinkNode(- 1 ) ;
private LinkNode tail = root ;
private int size ;
public MyLinkedQueue ( int maxQueueSize) {
this . MaxQueueSize = maxQueueSize ;
}
public int size () {
return size ;
}
public boolean isfull () {
return size == MaxQueueSize ;
}
public void enqueue ( int value) {
if ( size < MaxQueueSize ) {
LinkNode tmpValue = new LinkNode(value) ;
tail . next = tmpValue ;
tail = tmpValue ;
size ++ ;
}
}
public int dequeue () {
int getvalue = - 1 ;
if ( size > 0 ) {
LinkNode linkNode = root . next ;
getvalue = linkNode. val ;
root . next = linkNode. next ;
if ( null == root . next ) {
tail = root ;
}
size -- ;
}
return getvalue ;
}
}
并发队列
这是一种特殊性质的队列,即在一个队列的实现中,加入了阻塞操作。当队列为空,队头取数据就会被阻塞,当队列满了,队尾放数据就会被阻塞,这很好的协调了两端速度不一至的问题,这个在实际的软件开发过程中非常的常用,就是我们平时开发中所说的,“生产者-消费者模型”,
当生产者线程的速度过快,消费者线程来不及消费时,队列很快被填满,生产者线程就会被阻塞。待消费者线程将队列数据取走,再唤醒生产者线程继续生产。
还可以协调生产者与消费者的个数,来提高数据的处理速度。
并发队列即可以使用链表来实现,还可以使用数组来实现。
使用链表来实现的阻塞队列,可以支持一个无界的队列,可以用来处理很长的任务队列,但由于队列可能会非常的长,处理时间也可能会非常的长,所以针对时间不敏感的任务,就比较适合使用链表来实现的阻塞队列,比如定时调度的任务,后台消息发送队列等。
使用数组来实现的阻塞队列,只支持一个有限的数组的大小,当处理的任务超过队列时,可直接拒绝任务处理,这样就更加适合时间敏感的系统,比如秒杀,处理用户的请求等。但队列的大小,设置时也需要格外的注意, 队列太大,导致处理请求过多的等待,过小,又导致资源浪费。