队列:是一个特殊的线性表,遵循先进先出(FIFO)原则,不允许插队。在front端(队头)进行删除操作,在表的rear端(队尾)进行插入操作。和栈一样,队列是操作受限制的线性表。
队列的特点:
a. 线性表:数组/链表
b. FIFO
队列的分类:
顺序(单向)队列——Queue:只能在一段插入数据,另一端删除数据;
循环(双向)队列——Deque:每一段都可以进行插入数据和删除数据的操作。
优先队列:按优先级进行插入的队列——在插入数据的时候,进行排序
阻塞队列:在队列的基础上增加了阻塞操作,即当队列为空时,从队头取数据会被阻塞,直到队列中有数据时;当队列已满时,往队列插数据会被阻塞,直到队列中有空闲位置。(实现时涉及多线程,锁等概念)
队列的基本操作:
入队enquue(),放一个数据到队列尾部;
出队dequeue(),从队列头部取一个元素
队列的实现方式:顺序队列&链式
实践:使用数组实现一个单向队列
public class ArrayQueue {
private int data[]; //数据
private int head = 0;//队头
private int rear = 0;//队尾
private int queueLength = 0; //队列的大小
//构造函数
public ArrayQueue(int queueLength){
data = new int[queueLength];
this.queueLength = queueLength;
}
public void enqueue(int m){
if (isFull()){ //队列已满
// return; //如果队列满了什么都不做,在队列数据几进几出以后,队列的利用率会下降
//优化:当队列满了,且head!=0时,进行队列移动,腾出空间
//时间复杂度:最好情况下是O(1),最差情况下是O(n)
if(head >0){
int[] temp = new int[queueLength];
for(int i=0;i<queueLength;i++){
temp[i]=data[head+i];
}
data=temp;
}else{
return;
}
}else{
data[rear] = m;
rear++;
}
}
public int dequeue(){
if(isEmpty()){
return -1;
}else{
int pop = data[head]; //先进先出
head++;
return pop;
}
}
public boolean isFull(){
return rear == queueLength;
}
public boolean isEmpty(){
return head == rear;
}
}
单向队列的空间利用率问题,导致需要在队列已满时进行移动队列腾出多余空间;
因此循环队列的优势展示了出来。
Q:循环队列怎么判断队列已满?
A:a. 增加属性 size; 表示当前已经存储了n个数据
b. (rear + 1)%n == head; //为什么不是rear+1 == head:当队列刚开始head还是0,但是rear已经是n-1的时候,等式就不相等了
Q:循环队列怎么判断队列为空?
A:head == rear
public class CircleArrayQueue {
private int data[]; //数据
private int head = 0;//队头
private int rear = 0;//队尾
private int queueLength = 0; //队列的大小
private int size = 0; //实际存储的数据大小
//构造函数
public CircleArrayQueue(int queueLength){
data = new int[queueLength];
this.queueLength = queueLength;
}
public void enqueue(int m){
if (isFull()){ //队列已满
return;
}else{
data[rear] = m;
rear = (rear+1) % queueLength; //循环队列的rear不能无限制增长
}
}
public int dequeue(){
if(isEmpty()){
return -1;
}else{
int pop = data[head]; //先进先出
head = (head + 1) % queueLength;//循环队列的head不能无限制增长
return pop;
}
}
//循环队列是否已满
public boolean isFull(){
return (rear +1) % queueLength == head;
}
//循环队列是否为空
public boolean isEmpty(){
return head == rear;
}
}
Q:线程池里当任务满时,此时又来一个新任务,线程池是如何处理的?具体有哪些策略?这些策略又是如何实现的?
A:a. 排队:阻塞队列。
若在公平的情况下,即是FIFO。同时分为无限的队列(例如链表,容易出现OOM问题eg: LinkedBlockingQueue),和有限的队列(例如数组,Interger.MAX ≈ 21亿,在小型系统中,预估请求量的情况下,使用)。
b. 丢弃:不处理数据,直接抛出
阻塞队列的应用:
eg:数据同步系统的架构:将大量异源的数据进行汇总。在数据入Kafka之后,使用阻塞队列实现“队列的限容”,使系统处理更稳定,最后汇总到数据仓库BI System中