队列
特性
操作特性:
队列也是一种操作受限的线性表,只允许在一端插入和删除数据。
跟栈一样,队列也可以使用数据来实现,还可以使用链表来实现。
使用数组实现的队列叫做顺序队列,使用链表实现的队列叫做链式队列。
顺序队列
/** * 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 ; } }
并发队列
这是一种特殊性质的队列,即在一个队列的实现中,加入了阻塞操作。当队列为空,队头取数据就会被阻塞,当队列满了,队尾放数据就会被阻塞,这很好的协调了两端速度不一至的问题,这个在实际的软件开发过程中非常的常用,就是我们平时开发中所说的,“生产者-消费者模型”,
当生产者线程的速度过快,消费者线程来不及消费时,队列很快被填满,生产者线程就会被阻塞。待消费者线程将队列数据取走,再唤醒生产者线程继续生产。
还可以协调生产者与消费者的个数,来提高数据的处理速度。
并发队列即可以使用链表来实现,还可以使用数组来实现。
使用链表来实现的阻塞队列,可以支持一个无界的队列,可以用来处理很长的任务队列,但由于队列可能会非常的长,处理时间也可能会非常的长,所以针对时间不敏感的任务,就比较适合使用链表来实现的阻塞队列,比如定时调度的任务,后台消息发送队列等。
使用数组来实现的阻塞队列,只支持一个有限的数组的大小,当处理的任务超过队列时,可直接拒绝任务处理,这样就更加适合时间敏感的系统,比如秒杀,处理用户的请求等。但队列的大小,设置时也需要格外的注意, 队列太大,导致处理请求过多的等待,过小,又导致资源浪费。