队列

队列与栈类似,都是基于数组或者链表,都只有入和出两个操作,不过有两点不同

①栈先进后出,队列先进先出

②栈只需要一个栈顶指针,队列则需要一头一尾两个指针,这两个是当前队列的界限

 

队列的出队和入队该怎么做呢?

我们将使用循环队列,因为如果不形成循环队列,当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()加锁,但是锁粒度大,并发度会比较低

 

队列应用场景

①由于先进先出,符合先请求者先得到响应的原则,所以队列可以应用在任何有限资源池中,用于排队请求,比如数据库连接池等。实际上,对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过“队列”这种数据结构来实现请求排队。

当队满时,(tail+1)%n=head
posted @ 2022-04-17 16:56  codemelo  阅读(56)  评论(0)    收藏  举报