队列:以顺序的方式维护的一组数据集合,在一端添加数据,从另一端移除数据。习惯来说,添加的一端称为尾,移除的一端称为头。
通用接口
public interface Queue<E> {
    /**
     * 插入队列
     */
    boolean offer(E value);
    /**
     * 从队列中获取值并移除
     */
    E poll();
    /**
     * 从队列中获取值但不移除
     */
    E peek();
    /**
     * 检查队列是否已满
     */
    boolean isFull();
    /**
     * 检查队列是否不为空
     */
    boolean isEmpty();
}基于单向循环链表的简单实现
public class LinkedQueue<E> implements Queue<E>, Iterable<E> {
    //提供哨兵节点
    private Node<E> sentinel = new Node<E>(null, null);
    //提供尾节点
    private Node<E> 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<E> {
        Node<E> next;
        E value;
        public Node(Node<E> next, E value) {
            this.next = next;
            this.value = value;
        }
    }
    @Override
    public boolean offer(E value) {
        //在队尾插入元素,选择尾插法
        if (isFull()) {
            return false;
        }
        Node<E> node = new Node<>(sentinel, value);
        tail.next = node;
        tail = node;
        size++;
        return true;
    }
    @Override
    public E poll() {
        if (isEmpty()) {
            return null;
        }
        Node<E> 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<E> iterator() {
        return new Iterator<E>() {
            Node<E> p = sentinel.next;
            @Override
            public boolean hasNext() {
                return p != sentinel;
            }
            @Override
            public E next() {
                E value = p.value;
                p = p.next;
                return value;
            }
        };
    }
}基于循环数组的简单实现
实现前,介绍一下环形数组与数组的区别
- 对比普通数组,起点和终点更为自由,不用考虑数据移动(普通数组移除元素时需要移动其他元素)
- “环”意味着不会存在【越界】问题
- 数组性能更佳
- 环形数组比较适合实现有界队列、RingBuffer 等
public class ArraysQueue<E> implements Queue<E>, Iterable<E> {
    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<E> iterator() {
        return new Iterator<E>() {
            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次方大小。具体实现方式如下




















