ArrayDeque的基本用法
1 | public class DequeDemo { |
上面这段程序简单的演示了如果对双端队列的操作,下面给出上面代码的示意图:
上图是在执行完往双端队列加元素后,双端队列中元素的存储情况,由上图可以得出以下几点结论:
①ArrayDeque
默认分配16个空间的数组。数组下标从0-15。
②addFirst(arr[i])
操作是将元素从数组的最后一个位置向前依次存放。
③addLast(arr[i])
操作是将元素从数组的第一个位置依次向后存放。
上面的图是分别在执行一次pollFirst()
和 pollLast()
后双端队列中的存储情况。由上图可知一下几点:
①如果单独将addFirst(arr[i])
和pollFirst()
结合使用的话,先进的元素会后出队,后进的元素反而先队,实现的是栈的功能。
②如果单独将addLast(arr[i])
和pollLast()
结合使用的话,先进的元素会后出队,后进的元素反而先队,实现的也是栈的功能。
再看下面的代码:
1 | public class DequeDemo { |
上面代码中依次从队头加入元素,然后在队尾删除元素下面是示意图:
使用addFirst(arr[i])
将元素全部加到双端队列中。
进行一次pollLast()
后队列中元素的情况。
进行3次pollLast()
后队列中元素的情况。
由上可以得出以下几点:
①如果单独将addFirst(arr[i])
和pollLast()
结合使用的话,先进的元素会先出队,后进的元素会后出队,实现的是普通队列。
②如果单独将addLast(arr[i])
和pollFirst()
结合使用的话,先进的元素会先出队,后进的元素会后出队,实现的也是普通队列。
深入了解ArrayDeque
下面从源码来看看ArrayDeque是如何实现上述功能的。
构造函数
默认构造函数新建了一个大小为16的数组。1
2
3
4public ArrayDeque() {
elements = (E[]) new Object[16];
//默认构造函数新建了一个大小为16的数组。
}
用户初始化大小的构造函数。1
2
3public ArrayDeque(int numElements) {
allocateElements(numElements);
}
上面的构造函数中只用到 allocateElements(numElements) 下面看看allocateElements(numElements) 的源码
1 | private void allocateElements(int numElements) { |
上面的这段代码做一下几点说明:
①当用户要求分配的数组大小小于8时,分配8个空间,当用户要求分配的数组大小为8时分配16个空间。
②当用户要求分配的数组大小大于8时则分配大于用户需要内存的2的最小幂,比如用户需要分配的数组大小为21时,系统会分配大小为32的数组。
③最大分配2 ^ 30 大小的数组。
传入容器的构造函数1
2
3
4
5
6public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
//分配数组
addAll(c);
// 将元素加入到 ArrayDeque中
}
下面看看 addAll(c) 的代码:
1 | public boolean addAll(Collection<? extends E> c) { |
下面利用 ArrayDeque(Collection<? extends E> c)
这个构造器来实现一个简单的例子:
1 | public class DequeDemo { |
上面代码中初始化时传入一个List的容器,下面示意图看看构造后的ArrayDeque是什么样的。
由上图可知:将list的元素加入到ArrayDeque中的时候是调用了addLast
方法,addList
方法是按顺序从前往后加入的。
addFirst
1 | public void addFirst(E e) { |
上面的tail 和 head到底是啥,还是画图来看看。
由上图可知
①addFirst 加一个元素时,head都会先减1,然后再放元素。head初始值为 0。
② (head - 1) & (elements.length - 1) 使head的值在【0, elements.length - 1】范围之内,同时可以循环利用数组里面的空间,head一直围着数组转圈。
③如果tail==head 的时候,由上图可知数组中的元素已经满了,所以会将数组的扩大一倍。初始时tail=0。
addLast
1 | public void addLast(E e) { |
由上面的图可知 addLast 时操作 tail 在tail位置加入元素,如果数组已经满了就扩容一倍。
pollFirst
1 | public E pollFirst() { |
下面是一个使用pollFirst()
的例子。
1 | public class DequeDemo { |
下面看看上面代码的流程图:
添加完元素后的双端队列。
执行一次pollFirst()
后。
执行两次pollFirst()
之后
执行三次pollFirst()
之后
执行6次pollFirst()
之后。
通过上面的图就很好理解pollFirst()
的删除流程了。
pollLast
1 | public E pollLast() { |
还是利用上面的那段代码只不过将pollFirst()
改为pollLast()
代码如下
1 | public class DequeDemo { |
依然上图:
添加完元素的队列。
执行一次pollLast()
后。
执行8次pollLast()
后。
执行9次pollLast()
后。
由上面可以总结出:
①每次pollLast()
前tail先减1,然后再删除,tail指向的位置在元素的上一个位置。
② pollLast()
也是绕着数组循环删除的。tail一直绕着数组循环转动。
removeFirst()
1 | public E removeFirst() { |
由上面代码可知其实removeFirst()
就是调用pollFirst()
不同之处在于在当pollFirst()
返回null removeFirst()
抛出 null 元素异常。
removeLast()
1 | public E removeLast() { |
由上面代码可知其实removeLast()
就是调用pollLast()
不同之处在于在当pollLast()
返回null removeLast()
抛出 null 元素异常。
add(E e)
1 | public boolean add(E e) { |
remove()
1 | public E remove() { |