Queue继承了Collection接口,是队列接口,它规范了队列的方法,Java中LinkedList实现了Queue接口,可以使用LinkedList来赋值给一个Queue引用,如:
Queue<Integer> queue=new LinkedList<>();
一、Queue的常用方法
(1)入队列
boolean offer(E e);
入队列成功返回true,失败返回false
(2)出队列
E poll(E e);
(3)看数据个数
int size();
(4)查看队列是否为空
boolean isEmpty();
(5)E peek();
查看队列首元素
二、Queue的实现方式
(1)链式结构
就是底层采用链表的方式实现,因为队列需要在两端都进行操作,所以需要有head和tail两个节点来维护首节点和尾结点来使出队列和入队列更加高效。
(2)顺序结构(循环队列)
顺序结构也就是使用数组来实现这个队列,一般的数组来实现队列,不管是采用头插尾出还是头出尾插,总会有一个效率特别低,需要将整个数组左右平移,所以正常情况下,数组是不好用来实现队列的,为了解决这个问题,出现了循环队列这种办法。
循环队列在物理结构上还是使用的一个普通的数组做来存储,但是逻辑上我们把这个数组想象成一个首尾相连的环。
循环队列使用front指向首元素的位置,rear指向的位置是下一次入队列的数放置的位置,初始状态下,rear==front,说明此时数组内没有元素。
入队列一次之后,rear就+1,直到rear指向了数组的最后一个位置,此时再入队列一个元素,循环队列将rear指向的位置赋值为这个元素,然后再+1,此时rear指向的位置就越界了,所以为了使rear不越界,每次rear+1之后,都要取数组长度的模,这样就能使rear能循环的首尾移动了。同理当出队列时,front也是+1再取模。
此时出现了一个问题,什么时候来判断这个队列满了呢?
你可能会说,每次入队列rare都+1,当入队列后rare等于front的时候,这个队列不就满了吗?
确实,这个说法很有道理,但是你别忘了,当数组没有元素的时候rear也等于front,你如何区分当rear==front时到底是队列满了呢?还是队列空了呢?
此时有两种方法:
(1)使用一个计数器count变量,入队列一个就++,出队列一个就--,当rear==front时,判断一下count是否等于0就知道此时是满队列还是空队列了
(2)浪费一个数组空间,也就是当rear+1==front时,我们就认为这个循环队列满了。
这就是循环队列实现的方式,我们可以发现,循环队列的效率其实是要比链普通式队列高的,循环队列出入队列不用创建节点,只需要改变一下front和rare就可以,但是循环队列是有容量的,链式队列不存在容量的概念。
综上:循环队列适合知道队列大概有多少个元素的的时候,它比普通链式队列要更高效。
三、双端队列Deque。
双端队列,也就是两端都可以出入的队列,普通队列只能一端入另一端出,java中的Queue也只提供了Offer和poll这两个方法来控制进出队列。Deque是双端队列它提供了更多的offer和poll方法。
(1)offerFirst(E e)和offerLast(E e)
分别代表从头结点入队列和从尾结点入。
(2)pollFirst()和pollLast()
从头结点出队列和从尾结点出队列
(3)peekFirst()和peekLast()
查看头结点元素和尾结点元素
可以看出Deque由于是双端队列,所以出入和查看队列都变成加了First或者Last的方法,但是deque因为是继承了queue接口的,也可以使用普通的offer,poll和peek方法,不过既然都使用Deque接口来实例化了,一般都是使用独属于Deque接口的方法
值得一提的是,LinkedList也实现了Deque接口,事实上,就是因为LinkedList实现了Deque接口,所以也实现了Queue接口。实现了Deque接口的还有ArrayDeque类,ArrayDeque的底层就是循环队列。