队列
队列与栈类似,都是基于数组或者链表,都只有入和出两个操作,不过有两点不同
①栈先进后出,队列先进先出
②栈只需要一个栈顶指针,队列则需要一头一尾两个指针,这两个是当前队列的界限
队列的出队和入队该怎么做呢?
我们将使用循环队列,因为如果不形成循环队列,当tail指到了数组尽头,再有入队操作时,就得扩容数组,还要进行数据迁移到前面已经出队的元素,覆盖已出队元素的位置(因为出队不是真的删除数组元素),性能会受影响,当然链表就不怕这种情况
比如下面这个,当我们再加入a和b入队时,tail从7向后移到了1
a,b入队
怎么样写好循环队列?
①确定好队空和队满的判定条件---------队空是head == tail ,队满时,(tail+1)%n=head。
public class CircularQueue { // 数组:items,数组大小:n private String[] items; private int n = 0; // head表示队头下标,tail表示队尾下标 private int head = 0; private int tail = 0; // 申请一个大小为capacity的数组 public CircularQueue(int capacity) { items = new String[capacity]; n = capacity; } // 入队 public boolean enqueue(String item) { // 队列满了 if ((tail + 1) % n == head) return false; items[tail] = item; tail = (tail + 1) % n; return true; } // 出队 public String dequeue() { // 如果head == tail 表示队列为空 if (head == tail) return null; String ret = items[head]; head = (head + 1) % n; return ret; } }
注意,这段代码的实现会导致:队满时,tail指向的是不保存有效值的内存,也就是会浪费掉队列队列的一个位置,为什么要这样呢,因为你细想,如果我们偏要不浪费最后这个元素的内存,那么就需要引入额外的变量和一些判断代码,综合考量下来,还是浪费一个元素内存更划算,更简单高效。
阻塞队列和并发队列
这两个队列实际应用略广泛
阻塞队列其实就是在当队列满了和空了的时后,你进行的入队和出队操作都会被阻塞,知道队列有空闲或者有元素的时候再进行。
阻塞队列经典应用就是“生产者-----消费者”模型
这个机制可以调节生产者和消费者的平衡,当生产者生产速度快于消费速度时,队列满了就会阻塞入队操作,也就先暂时停止生产,同样,当消费速度更快,则队列空时暂时阻塞出队操作,
而且还可以多线程操作
不过这时线程不安全,我们称线程安全为并发队列,最简单的实现方法就是enqueue()和dequeue()加锁,但是锁粒度大,并发度会比较低
队列应用场景
①由于先进先出,符合先请求者先得到响应的原则,所以队列可以应用在任何有限资源池中,用于排队请求,比如数据库连接池等。实际上,对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过“队列”这种数据结构来实现请求排队。