什么是队列Queue
什么是队列?
队列是数据结构中比较重要的一种类型(是一种数据结构),它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。
队列是一个有序列表,可以用数组或是链表来实现。使用数组的顺序队列,使用链表的为链式队列。
队列遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出。
特点
队列的尾部添加数据的;拉取数据是从队列头部拉取的,拉取完之后将该元素删除。
使用场景
一般情况下,如果是对一些及时消息的处理,并且处理时间很短的情况下是不需要队列的,直接阻塞式的方法调用就可以了。但是如果在消息处理的时候特别费时间,这个时候如果有新消息来了,就只能处于阻塞状态,造成用户等待。这个时候便需要引入队列了。当接收到消息后,先把消息房贷队列中,然后再用行的线程进行处理,这个时候就不会有消息阻塞了。
队列主要应用在下面几种场景
• 队列用于异步数据传输(例如,数据不以两个进程之间的相同速率传输)。 管道,文件IO,套接字。
• 队列在大多数应用程序中用作缓冲区,如MP3媒体播放器,CD播放器等。
• 队列用于维护媒体播放器中的播放列表,以便添加和删除播放列表中的歌曲。
• 队列在操作系统中用于处理中断。
时间复杂性
Queue的分类
- 双端队列:双端队列(Deque)是 Queue 的子类也是 Queue 的补充类,头部和尾部都支持元素插入和删除.
- 阻塞队列:在元素操作时(添加或删除),如果没有成功,会阻塞等待执行。例如,当添加元素时,如果队列元素已满,队列会阻塞等待直到有空位时再插入。
- 非阻塞队列:非阻塞队列和阻塞队列相反,会直接返回操作的结果,而非阻塞等待。双端队列也属于非阻塞队列
Queue的基本方法
- boolean add(E e) //将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
- E element() //获取,但是不移除此队列的头。
- boolean offer(E e) //将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常。
- E peek() //获取但不移除此队列的头;如果此队列为空,则返回 null。
- E poll() //获取并移除此队列的头,如果此队列为空,则返回 null。
- E remove() //获取并移除此队列的头。删除元素,成功返回 true,失败返回 false;
Queue的实现
1 非阻塞队列
- ConcurrentLinkedQueue:
基于链接节点的、线程安全的无界队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们
当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择 - 优先级队列priorityQueue:
一个基于优先级堆的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。优先级队列不允许使用 null 元素。非线程安全的,在多线程情况下可使用 PriorityBlockingQueue 类替
2 阻塞队列BlockingQueue
- BlockingQueue 提供了线程安全的队列访问方式,当向队列中插入数据时,如果队列已满,线程则会阻塞等待队列中元素被取出后再插入;
- 当从队列中取数据时,如果队列为空,则线程会阻塞等待队列中有新元素再获取
不接受 null 元素 可以是限定容量的 主要用于生产者-使用者队列
阻塞队列分为:
① LinkedBlockingQueue:
是一个由链表实现的无界阻塞队列,内部维持着一链表构成的缓冲队列其容量默认值为 Integer.MAX_VALUE,也可以自定义容量,建议指定容量大小,变成有界阻塞队列,只有
当缓冲区队列达到默认最大缓存容量Integer.MAX_VALUE或者指定的缓存容量时才会阻塞队列。其实现队列的锁是分离的来控制数据的同步,高并发可以提高整个队列的性能。即不同线程使用的锁不同
② PriorityBlockingQueue:
一个支持优先级排序的无界阻塞队列,其内部控制线程的同步锁采用的是公平锁
③ ArrayBlockingQueue:
是一个有边界的阻塞队列,它的内部实现是一个数组。它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变队列按 FIFO(先进先出)原则对元素进行排序,队列获取操作则是从队列头部开始获得元素, 内部的阻塞队列是通过可重入的互斥锁 ReentrantLock 和 Condition 条件队列实现的其锁没有实现分离不同的线程共用同一个锁,存在公平访问与非公平访问的区别,
其内部还保存着两个整型变量。分别标识着队列的头部和尾部,插入和删除不会产生或者销毁任何的对象实例
④ DelayQueue:
是一个支持延时获取元素的无界阻塞队列,队列中的元素必须实现 Delayed 接口,在创建元素时可以指定延迟时间,只有到达了延迟的时间之后,才能获取到该元素。实现了 Delayed 接口必须重写两个方法 ,getDelay(TimeUnit) 和 compareTo(Delayed) 此队列不允许使用 null 元素
⑤ SynchronousQueue:
一个不存储元素、没有内部容量无缓冲等待的阻塞队列。其中每个插入操作必须等待另一个线程的对应移除操作。此队列不允许 null 元素;使用的数据结构是双重队列(Dual queue)和双重栈(Dual stack)
⑥ LinkedTransferQueue:
一个由链表结构组成的无界阻塞TransferQueue队列。
transfer方法。如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
tryTransfer方法。则是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。
模拟队列
使用数组模拟队列
package DataStructure.Queue;
import java.util.Arrays;
import java.util.Scanner;
/**
* @author Jerssy
* @version V1.0
* @Description 队列
* @create 2021-02-06 14:46
*
* 队列介绍
* 队列是一个有序列表,可以用数组或是链表来实现。支持FIFO,尾部添加、头部删除(先进队列的元素先出队列)
*
* 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
*
* 都是从队列的尾部添加数据的;拉取数据是从队列头部拉取的,拉取完之后将该元素删除
*
* 队列有2中实现方式:数组和链表
* 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 maxSize 是该队列的最大容量。
* 非环形队列--存在假溢出现象--队列满后,将全部元素出队却不能继续添加元素的情况
*/
public class ArrayQueue {
public static void main(String[] args){
Queue<String> queue = new Queue<>(3);
Scanner scanner=new Scanner(System.in);
label:while (true){
System.out.println("s(show):显示队列");
System.out.println("e(exit):退出");
System.out.println("a(add):添加数据到队列");
System.out.println("g(get):获取队列数据");
System.out.println("h(head):获取队列的头部数据");
System.out.println("l(size):获取队列的长度");
System.out.println("c(clear):清空队列");
char c = scanner.next().charAt(0);
try {
switch (c){
case 's'-> queue.showQueue();
case 'e'-> {
break label;
}
case 'a'->{
System.out.println("请输入");
queue.addQueue(scanner.next());
}
case 'g'-> System.out.println("取出的数据为"+queue.popQueue());
case 'h'-> System.out.println("队列头部的数据为"+queue.peekQueue());
case 'l'->System.out.println("队列的长度为"+queue.size());
case 'c'->queue.clear();
}
}catch ( Exception e){
e.printStackTrace();
}
}
}
static class Queue< T>{
private final int maxSize;//队列的最大容量,即数组长度
private int head;//队列的头
private int tail;//队列的尾部
private final T[] arr;//存放数据,模拟队列
private int size=0; //队列长度
//创建队列的构造器
public Queue(int arrMaxSize){
if (arrMaxSize <= 0) throw new IndexOutOfBoundsException("arrMaxSize 不能为0");
maxSize=arrMaxSize;
arr= (T []) new Object[arrMaxSize];
head=0;//指向队列的头部
tail =0;//指向队列的尾部
}
//判断队列满
public boolean isFull(){
return size>=maxSize;
}
//判断队列为空
public boolean isEmpty(){
return size==0;
}
//添加数据到队列
public void addQueue(T n){
if (isFull()){
throw new RuntimeException("队列已满");
}
if (tail > maxSize-1){//改成循环队列
tail = 0;
}
arr[tail++]=n;
size++;
}
//数据出队列
public T popQueue() {
if (isEmpty()){
throw new RuntimeException("队列为空");
}
if (head> maxSize-1){//改成循环队列
head = 0;
}
size--;
return arr[head++];
}
//显示队列的所有数据
public void showQueue(){
if (isEmpty()){
throw new RuntimeException("队列为空");
}
for (int i = head; i <head+size ; i++) {
System.out.printf("arr[%s]=%s\n",i%maxSize, arr[i%maxSize]);
}
}
//显示队列的头部
public T peekQueue(){
if (isEmpty()){
throw new RuntimeException("队列为空");
}
return arr[head];
}
//队列的容量
public int size(){
return size;
}
// 清空队列
public void clear(){
Arrays.fill(arr, 0);
tail = head = 0;
size = 0;
}
}
}
使用单链表模拟队列
package DataStructure.Queue;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @author Jerssy
* @version V1.0
* @Description 链式队列
* @create 2021-02-07 19:51
*/
public class LinkedQueue {
public static void main(String[] args) {
Queue<String> queue = new Queue<>();
Scanner scanner=new Scanner(System.in);
label:while (true){
System.out.println("s(show):显示队列");
System.out.println("e(exit):退出");
System.out.println("a(add):添加数据到队列");
System.out.println("g(get):获取队列数据");
System.out.println("h(head):获取队列的头部数据");
System.out.println("l(size):获取队列的长度");
System.out.println("c(clear):清空队列");
char c = scanner.next().charAt(0);
try {
switch (c){
case 's'-> queue.show();
case 'e'-> {
break label;
}
case 'a'->{
System.out.println("请输入");
queue.offer(scanner.next());
}
case 'g'-> System.out.println("取出的数据为"+queue.pop());
case 'h'-> System.out.println("队列头部的数据为"+queue.peek());
case 'l'->System.out.println("队列的长度为"+queue.size());
case 'c'->queue.clear();
}
}catch ( Exception e){
e.printStackTrace();
}
}
}
static class Queue<T> {
int size=0;
private Node<T> head;
private Node<T> tail;
//入队列
public void offer(T t){
Node<T> node= new Node<>(t);
if(isEmpty()){
head=node;
}
else tail.next=node;
tail=node;
size++;
}
//出队列
public T pop(){
if(isEmpty()){
throw new RuntimeException("队列是空的");
}
T oldValue=head.item;
if (head.next==null){//就一个元素
head=tail=null;
}
else {
Node<T> newNode=head.next;
head.next=null;
head = null;
head=newNode;
}
size--;
return oldValue;
}
//判断队列是否为空
public boolean isEmpty(){
return size == 0;
}
//返回队列的头部
public T peek(){
return head.item;
}
//队列的长度
public int size(){
return size;
}
//打印队列
public void show(){
if(isEmpty()){
throw new RuntimeException("队列是空的");
}
Node<T> node=head;
List<String> list = new ArrayList<>();
while (node!= null){
list.add((String) node.item);
node = node.next;
}
System.out.println(String.join("-->", list));
}
//清空队列
public void clear(){
for (Node<T> node= head; node != null; ) {
Node<T> next = node.next;
node.item = null;
node.next = null;
node = next;
}
head = tail = null;
size = 0;
}
}
//节点类
static class Node<T>{
private T item;
private Node<T> next;
public Node(T item) {
this.item = item;
}
}
}