队列:以顺序的方式维护的一组数据集合,在一端添加数据,从另一端移除数据。习惯来说,添加的一端称为尾,移除的一端称为头。
- public interface Queue
{ - /**
- * 插入队列
- */
- boolean offer(E value);
- /**
- * 从队列中获取值并移除
- */
- E poll();
- /**
- * 从队列中获取值但不移除
- */
- E peek();
- /**
- * 检查队列是否已满
- */
- boolean isFull();
- /**
- * 检查队列是否不为空
- */
- boolean isEmpty();
- }
基于单向循环链表的简单实现
- public class LinkedQueue
implements Queue, Iterable { - //提供哨兵节点
- private Node
sentinel = new Node(null, null); - //提供尾节点
- private Node
tail = sentinel; - //队列大小
- private int size = 0;
- //队列容量
- private int capacity = Integer.MAX_VALUE;
-
- public LinkedQueue(int capacity) {
- this.capacity = capacity;
- tail.next = sentinel;
- }
-
- private static class Node
{ - Node
next; - E value;
-
- public Node(Node
next, E value) { - this.next = next;
- this.value = value;
- }
- }
-
- @Override
- public boolean offer(E value) {
- //在队尾插入元素,选择尾插法
- if (isFull()) {
- return false;
- }
- Node
node = new Node<>(sentinel, value); - tail.next = node;
- tail = node;
- size++;
- return true;
- }
-
-
- @Override
- public E poll() {
- if (isEmpty()) {
- return null;
- }
- Node
first = sentinel.next; - if (first==tail){
- //如果是最后一个节点,那么将tail指向sentinel
- tail =sentinel;
- }
- sentinel.next = first.next;
- E value = first.value;
- size--;
- return value;
- }
-
- @Override
- public E peek() {
- return sentinel.next.value;
- }
-
- @Override
- public boolean isFull() {
- return size == capacity;
- }
-
- @Override
- public boolean isEmpty() {
- return size == 0;
- }
-
- @Override
- public Iterator
iterator() { - return new Iterator
() { - Node
p = sentinel.next; -
- @Override
- public boolean hasNext() {
- return p != sentinel;
- }
-
- @Override
- public E next() {
- E value = p.value;
- p = p.next;
- return value;
- }
- };
- }
- }
基于循环数组的简单实现
实现前,介绍一下环形数组与数组的区别
- public class ArraysQueue
implements Queue, Iterable { - private int head = 0;
- private int tail = 0;
- //用来记录循环数组大小
- private final int length;
- private E[] array;
-
- @SuppressWarnings("all")
- public ArraysQueue(int capacity) {
- this.length = capacity + 1;
- //加一是为尾指针留一个空间去判断是否队列已满
- this.array = (E[]) new Object[length];
- }
-
- @Override
- public boolean offer(E value) {
- if (isFull()) {
- return false;
- }
- array[tail++] = value;
- tail = tail % length;
- return true;
- }
-
- @Override
- public E poll() {
- if (isEmpty()) {
- return null;
- }
- E value = array[head];
- head = (head + 1) % length;
- return value;
- }
-
- @Override
- public E peek() {
- if (isEmpty()) {
- return null;
- }
- return array[head];
- }
-
- @Override
- public boolean isFull() {
- if ((tail + 1) % length == head) {
- return true;
- }
- return false;
- }
-
- @Override
- public boolean isEmpty() {
- return head == tail;
- }
-
- @Override
- public Iterator
iterator() { - return new Iterator
() { - int p = head;
-
- @Override
- public boolean hasNext() {
- return p != tail;
- }
-
- @Override
- public E next() {
- E value = array[p];
- p = (p + 1) % length;
- return value;
- }
- };
- }
- }
在Java源码中的基于数组实现的队列对容量有一个要求,即一定是2的n次方。之所以这么要求,是因为方便头指针和尾指针的边界确认。
我们的实现方式中指针的值是通过+1并取余来确定指针的下一个位置,也就是说,head和tail的值始终是在数组长度中。而在Java源码中,并没有规定head与tail的取值一定是数组长度内,而是不停的+1然后通过对数组长度的取余,来确定head与tail的下标位置。
但是这样又存在一个问题。那就是head或是tail超过了int类型所能表达的最大值后,再去取余会得到负数,使用负数去数组中拿元素会报错。为了解决这个问题,Java针对二进制特点采用了更高效率的实现方案。
首先就是规定数组长度一定是2的n次方。
看下面例子

因此我们不需要在意符号位是否为负数,只需要关心余数的二进制即可。
对于如何通过二进制的方式获取到余数。是二进制的另一个特性

我们查看ArrayDeque源码中的添加元素方法

正是采用了二进制的位运算特性来控制head与tail在数组中的下标位置。如果用户指定数组队列不是一个2的n次方时,他会强制扩容到最近的2的n次方大小。具体实现方式如下
