队列的简单理解

​ 队列(Queue)是一个先进先出(FIFO)的数据结构,下面我们直接来看Java里的实现。Queue在Java里作为一个接口,在java.util包下继承自Collection,共有六个方法如下。

方法 用处 出错时
boolean add(E e); 向队列尾部插入一个元素 超长抛异常
boolean offer(E e); 向队列尾部插入一个元素 超长返回false
E remove(); 移除并返回队首元素 队列为空时产生异常
E poll(); 移除并返回队首元素 队列为空时返回null
E element(); 获取队首元素 队列为空时产生异常
E peek(); 获取队首元素 队列为空时返回null

总结起来就是三种操作,插入,删除,和查询,分别有对应的异常和返回值方法。

插入

删除

查询

​ 以上是基本的队列只能在尾部插入,头部移除,我们给他丰富一下功能,双端都可以插入和移除,这就是双向队列(Deque),来看一下Java的Dequne接口的方法,位于java.util下,这里主要查看Deque的一些方法,忽略了,Queue的方法和Stack的方法已及实现Collection的方法

方法 说明 出错时
void addFirst(E e); 向队首插入元素 超长时抛异常
void addLast(E e); 向队尾插入元素 超长时抛异常
boolean offerFirst(E e); 向队首插入元素 超长时返回false
boolean offerLast(E e); 向队尾插入元素 超长时返回false
E removeFirst(); 移除并返回队首元素 队列为空抛异常
E removeLast(); 移除并返回队尾元素 队列为空抛异常
E pollFirst(); 移除并返回队首元素 队列为空返回null
E pollLast(); 移除并返回队尾元素 队列为空返回null
E getFirst(); 获取队首元素 队列为空抛异常
E getLast(); 获取队尾元素 队列为空抛异常
E peekFirst(); 获取队首元素 队列为空时返回null
E peekLast(); 获取队尾元素 队列为空时返会null

总结起来就是Queue的方法x2,分别也是 插入,删除,查询

插入

删除

查询

Deque包含了Queue的功能,我们直接来看一下Deque的实现类ArrayDeque的代码,这里是用数组实现了队列,同样也有用链表实现队列的,那就比较容易理解了,链表删除表头和表尾元素都不难,扩充也简单。对于数组实现队列来说,扩充容量肯定是要解决的问题,我猜想扩容的方法和之前的数组篇没啥大体区别。先来看插入

public void addFirst(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;
    if (head == tail)
        doubleCapacity();
}

主要看下面句,其余的都比较好理解,doubleCapacity是扩容函数稍后再看

elements[head = (head - 1) & (elements.length - 1)] = e;

&是与操作,都为1的位为1,其余为0。这里的elements.length - 1作为一个掩码来使用,光这么看理解起来有些晦涩,直接看图吧,有两种情况,第一种是head为0时

巧妙的运用了&操作使数组首尾相接,第二种时head不为0时

这个就比较好理解了,接下来再看addLast

public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[tail] = e;
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}

因为需要判断在head==tail的时候来扩容,所以这里的tail都是指向没有元素的格子的,相反的head都是指向队首元素所在的位置的,所以addFirst和addLast再插入元素的时候稍微有点区别。addLast是先写入再增加,还是看这句

 if ( (tail = (tail + 1) & (elements.length - 1)) == head)

依然是两种情况第一种是tail加完之后等于队列的长度

巧妙的&操作让队列首位相连,当tail部位队列长度-1时

接下来看看这个doubleCapacity函数是怎么个原理

private void doubleCapacity() {
    assert head == tail;	//确保头尾相等
    int p = head;	
    int n = elements.length;
    int r = n - p; // number of elements to the right of p  
    int newCapacity = n << 1;	//扩大二倍
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    Object[] a = new Object[newCapacity];	//创建新的数组
    System.arraycopy(elements, p, a, 0, r);	//复制head之后的元素
    System.arraycopy(elements, 0, a, r, p); //复制head之前的元素
    elements = a;
    head = 0;
    tail = n;
}

这个函数arraycopy这部分不太好理解,还是结合图来说

数组扩容总是放生在add操作上,无论是head后边追上tail还是tail后退碰到head,都是tail在前,head在后,r,p都如图中所示。

删除操作和这个增加操作其实是相对应的头删除对应尾插入,尾删除对应头插入,一样用&来连接,如果队列为空了,取不到元素了就会返回Null或者报错了,本来还想写一下优先队列的,感觉篇幅有点长了还是单开一文吧

总结

队列Queue有先进先出的特点,Deque可以两头进出,ArrayDeque的位操作可谓十分精髓,一定要拜读一下源码。

posted @ 2020-04-30 18:25  林静生寒  阅读(580)  评论(0编辑  收藏  举报