队列
- 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
- 进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
- 队列的数据元素又称为队列元素。
- 在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表,LIFO。
特点
- 线性表:链表或者数组
- FIFO
队列分类
- 顺序队列(Queue) 只能在一端插入数据,另一端删除数据
- 循环(双向)队列(Deque):每一端都可以进行插入数据和删除数据操作
队列基本操作者
我们知道,栈只支持两个基本操作:入栈push()和出栈pop()。
队列跟栈非常相似,支持的操作也很有限,最基本的操作也是两个:
- 入队enqueue(),放一个数据到队列尾部;
- 出队dequeue(),从队列头部取一个元素。
所以,队列跟栈一样,也是一种操作受限的线性表数据结构。作为一种非常基础的数据结构,队列的应用也非常广泛,例如AQS框架里维护了一个CLH队列、条件等待队列,线程池里的等待队列,RabbitMQ和其他消息中间件等。
队列实现方式
基于数组实现的顺序队列
package com.DataConstruct.queue;
public class MyArrayQueue implements MyQueue{
private Object[] table;
private int top=0;
private int defaultCapacity=10;
private int head=0;
public MyArrayQueue() {
table=new Object[defaultCapacity];
}
@Override
public void push(Object val) {
if(val==null) return;
if (top>table.length)
throw new RuntimeException("队列满了");
table[top++]=val;
}
@Override
public Object pop() {
if(head==top)
throw new RuntimeException("队列为空");
return table[head++];
}
public Object peek() {
if(head==top)
throw new RuntimeException("队列为空");
return table[head++];
}
@Override
public int size() {
return top ;
}
public void print(){
for (int i=head;i<top;i++){
System.out.println(table[i].toString());
}
}
public static void main(String[] args) {
MyArrayQueue queue=new MyArrayQueue();
queue.push("dd");
queue.push("CC");
queue.push("AA");
queue.push("WW");
queue.print();
System.out.println("-----------------");
System.out.println(queue.pop());
System.out.println(queue.pop());
System.out.println("--------------");
queue.print();
}
}
基于链表的队列实现
package com.DataConstruct.queue;
import com.DataConstruct.stack.MyLinkedStack;
public class MyLinkedQueue implements MyQueue {
private Node head;
private Node tail;
private int top=0;
public MyLinkedQueue() {
}
@Override
public void push(Object val) {
Node newNode=new Node(val);
if(head==null){
head=newNode;
tail=newNode;
}else {
tail.next=newNode;
tail=newNode;
}
top++;
}
@Override
public Object pop() {
if(isEmpty())return -1;
Object result=head.data;
head=head.next;
top--;
return result;
}
@Override
public int size() {
return top;
}
@Override
public Object peek() {
if(isEmpty())return -1;
return head.data;
}
public boolean isEmpty(){
if(top==0){
return true;
}else {
return false;
}
}
public void print(){
Node temp=head;
while (temp!=null){
System.out.println(temp.data);
temp=temp.next;
}
}
public static void main(String[] args) {
MyLinkedQueue stack=new MyLinkedQueue();
stack.push("dd");
stack.push("ad");
stack.push("cd");
stack.push("ed");
stack.print();
System.out.println("------");
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println("------");
stack.print();
System.out.println("------");
System.out.println(stack.peek());
}
}
class Node {
Object data;
Node next;
Node pre;
public Node(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
就是链表的特殊操作而已。
两种队列的实现都有各自的问题:
- 数组队列元素不能复用;
- 链表队列无限容量;
针对数组队列出队后,数组元素就不能再使用了,而我们的数组内存是一次性申请好的,申请完了后,不再使用但是依旧占用着内存,存在空间浪费;
解决方法:
- 在数组队列里,当push数据时,队列满时,就去移动数组元素的位置。
- 使用循环队列
循环队列
怎么判断循环队列满了?
- 添加一个变量统计队列里存在元素的个数,当和容量相等时,则满了
- (tail+1)%n==head
实现:
package com.DataConstruct.queue;
public class CircleQueue implements MyQueue {
private Object[] data;
private int head=0;
private int tail=0;
private int capacity;
private final static int defaultCapacity=10;
public CircleQueue() {
this(defaultCapacity);
}
public CircleQueue(int capacity) {
data=new Object[capacity];
this.capacity=capacity;
}
@Override
public void push(Object val) {
if (isFull()){
return;
}else {
data[tail]=val;
tail=(tail+1)%capacity;
}
}
@Override
public Object pop() {
System.out.println("head==>"+head + " "+" tail==>"+tail);
if (isEmpty()){
return -1;
}
Object result=data[head];
head=(head+1)%capacity;
return result;
}
@Override
public int size() {
return (tail-head+capacity)%capacity ;
}
@Override
public Object peek() {
if (isEmpty()){
return -1;
}
return data[head];
}
public boolean isFull(){
if((tail+1)% capacity== head){
return true;
}else {
return false;
}
}
public boolean isEmpty(){
if(tail==head){
return true;
}else {
return false;
}
}
public void print(){
int temp=head;
for (int i=temp;temp!=tail%capacity;temp=(temp+1)%capacity){
System.out.println(data[temp]);
}
}
public static void main(String[] args) {
CircleQueue circleQueue=new CircleQueue();
circleQueue.push("11");
circleQueue.push("22");
circleQueue.push("33");
circleQueue.push("44");
circleQueue.push("55");
circleQueue.push("66");
circleQueue.push("77");
circleQueue.push("88");
circleQueue.push("dd");
circleQueue.print();
System.out.println("==============");
System.out.println("========pop start=======");
System.out.println(circleQueue.pop());
System.out.println(circleQueue.pop());
System.out.println(circleQueue.pop());
System.out.println("===pop end============");
circleQueue.print();
}
}
队列应用场景
- AQS用于存储获取锁的线程的队列;
- 线程池的阻塞队列
- 消息中间件
- LRU算法
一般都是异步队列,存储一些消息或者任务,然后异步的运行,降低耦合度,提高响应速度。